简体   繁体   English

JavaScript变量分配/返回

[英]JavaScript variable assignment/return

I am loading 2 csv files using d3.js and would like to merge them. 我正在使用d3.js加载2个csv文件,并希望将它们合并。 I am stuck, however, on something much more basic. 但是,我仍然停留在更基本的东西上。

I have the following function that works fine: 我有以下功能正常工作:

function loadData(file) {
   d3.csv(file, function (d){...}, 
        function (data) {displayData(data);});
}

Now I am trying to refactor the code in a way I have loadData() to return data object, so I can call it twice, merge data arrays and call displayData() with the merged array. 现在,我尝试以一种具有loadData()返回data对象的方式来重构代码,因此我可以调用它两次,合并data数组,并使用合并的数组调用displayData()

I tried returning data : 我尝试返回data

function loadData(file) {
   d3.csv(file, function (d){...}, 
        function (data) {return data});

   return data;
}

using a global variable 使用全局变量

 var gdata1 = {};
 var gdata2 = {};
 function loadData(file) {
   d3.csv(file, function (d){...}, 
        function (data) {gdata = data});

   gdata2 =  data;
}

among many other things, nothing seems to work. 除其他外,似乎没有任何作用。

Surprisingly, 出奇,

using a global variable 使用全局变量

 var gdata1 = {};
 var gdata2 = {};
 function loadData(file) {
   d3.csv(file, function (d){...}, 
        function (data) {gdata = data; displayData(gdata)});


}

works fine. 工作正常。

Can anyone please explain what is the best/right way of getting my data array out of displayData function and how to merge two data arrays (I expect data to be an array of maps, eg data[0] is a map). 谁能解释一下将数据数组从displayData函数中移出的最佳/正确方法是什么,以及如何合并两个数据数组(我希望数据是映射数组,例如data [0]是映射)。

Promises help you handle several things that are a bit unpleasant with callbacks. Promise帮助您处理一些回调不愉快的事情。 you should take a look at them. 你应该看看他们。

And this tiny d3 plugin will make it work: https://github.com/kristw/d3.promise 这个小巧的d3插件将使其正常工作: https//github.com/kristw/d3.promise

Promise.all([
    d3.promise.csv(url1, formatter),
    d3.promise.csv(url2, formatter)
]).then(function(results){
    console.log('all files have been loaded');

    var formatedDataFromUrl1 = results[0];
    var formatedDataFromUrl2 = results[1];

    //proceed/combine/render the results as you wish
});

So basically d3.promise.csv replaces your loadData function. 因此,基本上d3.promise.csv取代了您的loadData函数。

Or you wrap it up as following to always use the same formatter: 或者您将其包装如下,以始终使用相同的格式化程序:

function loadData(file) {
    return d3.promise.csv(file, function (d){...});
}

Edit: 编辑:

unfortunately I cannot use any plugins, only "core" d3 不幸的是,我不能使用任何插件,只能使用“核心” d3

Then you can basically copy-paste the whole plugin into your code, it's not that much, really ;) 然后,您基本上可以将整个插件复制粘贴到您的代码中,实际上并没有那么多;)

For this special case, the core functionality can be boiled down to: 对于这种特殊情况,核心功能可以归结为:

function loadCsv(url){
    return new Promise(function(resolve, reject){
        d3.csv(url, function (d){...}, function(err, data){
            if(err) reject(Error(err));
            else resolve(data);
        });
    });
}

The plugin pretty much just wraps a few more methods (like json, xml, ...) the same way, and is therefore a tiny bit more generic. 该插件几乎以相同的方式包装了更多方法(例如json,xml等) ,因此更加通用。 You should take a look at the source code. 您应该看一下源代码。

loadData() should take a callback. loadData()应该接受回调。 Then you can load the second file in the callback of the first file. 然后,您可以在第一个文件的回调中加载第二个文件。

function loadData(file, callback) {
    d3.csv(file, function(d) { ...}, callback);
}

loadData(file1, function(err1, data1) {
    loadData(file2, function(err2, data2) {
        // code to combine data1 and data2 and display result
    });
});

This has the disadvantage that it serializes the file accesses, so it's not as performant as using promises with Promise.all() , as in Thomas's answer. 这样做的缺点是它会序列化文件访问,因此它的性能不如在Thomas的答案中与Promise.all()一起使用promises一样。

To deal with an arbitrary number of files, you can pull them from an array, using a variable that you increment each time. 要处理任意数量的文件,可以使用每次递增的变量从数组中提取文件。

function loadNextFile(files, i, dataArray) {
    if (i >= files.length) {
        // merge dataArray and display it
    } else {
        loadData(files[i], function(err, data) {
            dataArray.push(data);
            loadNextFile(files, i+1, dataArray);
        }
    }
}
var filesToLoad = [...];
loadNextFile(filesToLoad, 0, []);

Managing the state of multiple concurrent requests and then syncing the results can be quite some work. 管理多个并发请求的状态,然后同步结果可能是很多工作。

Managing state is one of the main purposes of Promises, and Promise.all is syncing and merging the results. 管理状态是Promises的主要目的之一,Promise.all正在同步和合并结果。

That's also the main purpose of the following code. 这也是以下代码的主要目的。 Two things left to say: 还有两件事要说:

  • this code ain't tested, it may contain some errors 该代码未经测试,可能包含一些错误

  • I've commented pretty much everything in this code four you to understand what its purpose is/mechanics are, and what it is capable of, and how to approach different use cases for this monster. 我已经在代码四中注释了几乎所有内容,以了解它的目的/机制是什么,它有什么功能,以及如何处理该怪物的不同用例。 That's why this answer ended up so darn long. 这就是为什么这个答案结局如此之久的原因。

Since the actual code to load a single file was so short and isolated, I decided to put that into an external function so you can reuse this whole code by only passing a different utility-function to do the actual request. 由于加载单个文件的实际代码非常短且孤立,因此我决定将其放入外部函数中,以便仅通过传递不同的实用程序函数来执行实际请求即可重用整个代码。

And because I prefer named mappings over plain arrays accessed by index (it's easier to not confuse names than indices) , I've integrated this possibility too. 而且由于我更喜欢​​命名映射而不是索引访问的普通数组(不混淆名称比索引更容易) ,所以我也集成了这种可能性。 If you don't know exactly what I mean by that, take a look at the examples after the main function. 如果您不清楚我的意思,请看一下main函数后面的示例。

And as additional sugar, and since it took only a minor tweak, I've made the returned function recursive, so it can deal with pretty much everything you pass to it as a "list" of urls. 作为额外的功能,由于只做了很小的调整,我就使返回的函数具有递归性,因此它可以处理作为URL“列表”传递给它的几乎所有内容。

function loadFilesFactory( loadFile ){
    function isNull(value){ return value === null }

    //takes an array of keys and an array of values and returns an object mapping keys to values.
    function zip(keys, values){
        return keys.reduce(function(acc, key, index){
            acc[key] = values[index];
            return acc;
        }, Object.create(null));  //if possible
        //}, {});  //if Object.create() doesn't work on the browser you need to support
    }

    //a recursive function that can take pretty much any composition as "url"
    //and will "resolve" to a similar composition of results, 
    //while loading everything in paralell
    //see the examples
    var recursiveLoadFilesFunction = function(arg, callback){
        if(arg !== Object(arg)){
            //arg is a primitive
            return loadFile(arg, callback);
        }

        if(!Array.isArray(arg)){
            //arg is an object
            var keys = Object.keys(arg);
            return recursiveLoadFilesFunction(keys.map(function(key){
                return arg[key];
            }), function(error, values){
                if(error) callback(error)
                else callback(null, zip(keys, values));
            });
        }

        //arg contains an array
        var length = arg.length;
        var pending = Array(length)
        var values = Array(length);

        //If there is no request-array anymore, 
        //then some (sync) request has already finished and thrown an error
        //no need to proceed 
        for(var i = 0; pending && i<length; ++i){
            //I'd prefer if I'd get the request-object to be able to abort this, in case I'd need to
            pending[i] = recursiveLoadFilesFunction(
                arg[i], 
                createCallbackFor(i) 

            //but if I don't get a sufficient value, I need at least to make sure that this is not null/undefined
            ) || true;
        }

        var createCallbackFor = function(index){
            return function(error, data){
                //I'm done, this shouldn't have been called anymore
                if(!pending || pending[index] === null) return;

                //this request is done, don't need this request-object anymore
                pending[index] = null;
                if(error){
                    //if there is an error, I'll terminate early 
                    //the assumption is, that all these requests are needed
                    //to perform, whatever the reason was you've requested all these files.
                    abort();
                    values = null; 
                }else{
                    //save this result
                    values[index] = data;
                }

                if(error || pending.every( isNull )){
                    pending = null; //says "I'm done"
                    callback(err, values);
                }
            }
        }

        var abort = function(){
            if(pending){
                //abort all pending requests
                pending.forEach(function(request){
                    if(request && typeof request.abort === "function") 
                        request.abort();
                });
                //cleanup
                pending = null;
            }
        }

        return { 
            //providing the ability to abort this whole batch.
            //either manually, or recursive
            abort: abort 
        }
    }

    return recursiveLoadFilesFunction;
}

This is the only part, that would change if you'd want to reuse this whole thing for let's say JSON files, or a different csv-formatting, or whatever 这是唯一的部分,如果您想将整个事情重用于JSON文件或其他csv格式或其他任何内容,那部分将有所更改

var loadCsvFiles = loadFilesFactory(function(url, callback){

    if(!url || typeof url !== "string"){
        callback(JSON.stringify(url) + ' is no valid url');
        return;
    }

    return d3.csv(url, function(d){ ... }, callback);
});

what can this code handle? 此代码可以处理什么?

//plain urls, sure
loadCsvFiles('url', function(err, result){ ... })

//an array of urls, it's inital purpose
loadCsvFiles(['url1', 'url2', 'url3'], function(err, results){
    console.log(results[0], results[1], results[2]);
});

//urls mapped by property names, I've already mentioned that I prefer that over array indices
loadCsvFiles({
    foo: 'file1.csv',
    bar: 'file2.csv'
}, function(err, results){
    //where `results` resembles the structure of the passed mapping
    console.log(results.foo, results.bar);
})

//and through the recursive implementation, 
//pretty much every imaginable (non-circular) composition of the examples before
//that's where it gets really crazy/nice
loadCsvFiles({
    //mapping a key to a single url (and therefoere result)
    data: 'data.csv',

    //or one key to an array of results
    people: ['people1.csv', 'people2.csv'],

    //or a key to a sub-structure
    clients: {
        jim: 'clients/jim.csv',
        //no matter how many levels deep
        joe: {
            sr: 'clients/joe.sr.csv',
            jr: 'clients/joe.jr.csv',
        },
        //again arrays
        harry: [
            'clients/harry.part1.csv', 
            'clients/harry.part2.csv', 
            //and nested arrays are also possible
            [
                'clients/harry.part3a.csv',
                'clients/harry.part3b.csv'
            ]
        ]
    },

    //of course you can also add objects to Arrays
    images: [
        {
            thumbs: 'thumbs1.csv',
            full: 'full1.csv'
        },
        {
            thumbs: 'thumbs2.csv',
            full: 'full2.csv'
        }
    ]
}, function(err, results){
    //guess what you can access on the results object:
    console.log(
        results.data,
        results.people[0],
        results.people[1],
        results.clients.jim,
        results.clients.joe.sr,
        results.clients.joe.jr,
        results.clients.harry[0],
        results.clients.harry[1],
        results.clients.harry[2][0],
        results.clients.harry[2][1],
        results.images[0].thumbs,
        results.images[0].full,
        results.images[1].thumbs,
        results.images[1].full
    )
});

Especially this last example may not make any sense to you, in terms of an absurd structure for csv-files, but that's not the point. 特别是对于csv文件的荒谬结构而言,最后一个示例对您可能没有任何意义,但这不是重点。 The point is, that it is completely up to you how you structure your data. 关键是,这完全取决于您如何构造数据。 Just pass it to this file loader and it will handle that. 只需将其传递给此文件加载器,它将进行处理。


And if you want this to support multiple file formats at once, it is also possible with a simple tweak: 而且,如果您希望它一次支持多种文件格式,则可以通过简单的调整来实现:

var loadDifferentFiles = loadFilesFactory(function(url, callback){
    if(!url || typeof url !== "string"){
        callback(JSON.stringify(url) + ' is no valid url');
        return;
    }

    if(url.endsWith('.csv')){
        return d3.csv(url, callback);
    }

    if(url.endsWith('.json')){
        return d3.json(url, callback);
    }

    //return d3.text(url, callback);
    callback('unsupported filetype: ' + JSON.stringify(url));
});

or sth. 或某物 like this 像这样

var loadDifferentFiles = loadFilesFactory(function(value, callback){
    if(typeof value !== "string"){
        if(value.endsWith('.csv')){
            return d3.csv(value, callback);
        }

        if(value.endsWith('.json')){
            return d3.json(value, callback);
        }
    }

    //but in this case, if I don't know how to handle a value
    //instead of resolving to an error, just forwarding the passed value to the callback, 
    //implying that it probably wasn't meant for this code.
    callback(null, value);
});

Thanks everyone. 感谢大家。 I ended up calling the function recursively: 我最终以递归方式调用了该函数:

var files = ['file1', 'file2', ...]
var alldata = [];


function loadData(files) {
   if(files.length == 0)
   {
    displayData('', alldata);
    return;
   }
   d3.csv(files[0],


    function(error, data) {
    ....
    alldata = alldata.concat(data);
    files.shift()
    loadData(files);
   });
}

I am sure the other solutions work too. 我相信其他解决方案也可以。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM