Node - 等待循环结束?

9
当下面的函数完成并提供数组“albums”中项目的最终列表时,希望它调用另一个函数/执行其他操作。目前在函数完成之前,它会发布[],我知道这是由于异步执行引起的,但我认为Node读取线性,因为它是单线程的?
function getAlbumsTotal(list, params){
    for(var i = 0; i<list.length; i++){
        api.getArtistAlbums(list[i], params).then(function(data) {
            for(var alb = 0; alb<data.body.items.length; alb++){
                albums.push(data.body.items[alb].id);
            }
        }, function(err) {
            console.error(err);
        });
    }
    console.log(albums);
    //do something with the finalized list of albums here
}
2个回答

13

你提供给then的回调函数确实是异步执行的,这意味着它只会在当前调用栈中的其余代码(包括最终的console.log)执行完成后才执行。

以下是如何操作:

function getAlbumsTotal(list, params){
    var promises = list.map(function (item) { // return array of promises
        // return the promise:
        return api.getArtistAlbums(item, params)
            .then(function(data) {
                for(var alb = 0; alb<data.body.items.length; alb++){
                    albums.push(data.body.items[alb].id);
                }
            }, function(err) {
                console.error(err);
            });
    });
    Promise.all(promises).then(function () {
        console.log(albums);
        //do something with the finalized list of albums here
    });
}

NB: 显然 albums 被定义为全局变量。这并不是一个很好的设计。更好的做法是每个 Promise 提供自己的一部分相册,Promise.all 调用会将这些结果连接成一个本地变量。以下是该做法的示例:

function getAlbumsTotal(list, params){
    var promises = list.map(function (item) { // return array of promises
        // return the promise:
        return api.getArtistAlbums(item, params)
            .then(function(data) {
                // return the array of album IDs:
                return Array.from(data.body.items, function (alb) {
                    return alb.id;
                });
            }, function(err) {
                console.error(err);
            });
    });
    Promise.all(promises).then(function (albums) { // albums is 2D array
        albums = [].concat.apply([], albums); // flatten the array
        console.log(albums);
        //do something with the finalized list of albums here
    });
}

这是最优雅的方法吗?如果我不使用.then,而是像这样使用会怎么样?function getAlbumsTotal(list, params){ for(var i = 0; i这会是同样的解决方案吗?编辑:我想它不会添加空格,而是函数的函数。 - Ralph
异步性仍然存在:您仍然需要一个回调函数,在当前正在运行的任何其他内容之后执行。因此,实际上,使用Promise会更好,因为有像Promise.all这样将所有内容汇集在一起的东西。如果没有它,您将不得不开始计算回调的最后一次调用。 - trincot
1
我扩展了我的答案,使用本地变量“albums”而不是全局变量,这样(根据您对其执行的其他操作)将更好地设计。 - trincot

1
如果你想在node.js中使用循环返回的数据,你需要添加一些额外的代码来检查是否处于循环的最后一次迭代。基本上,你需要编写自己的“循环完成”检查,并且仅当该条件为真时才运行。
我已经写了一个完整可运行的示例,以便您可以拆分它并了解其工作原理。重要的部分是添加计数器,每次循环后递增它,然后检查当计数器与正在迭代的列表的长度相同时。
function getArtistAlbums(artist, params){
  var artistAlbums = {
    'Aphex Twin':['Syro', 'Drukqs'],
    'Metallica':['Kill \'Em All', 'Reload']
  };
  return new Promise(function (fulfill, reject){
    fulfill(artistAlbums[artist]);
  });

}
function getAlbumsTotal(list, params){
  var listCount = 0;
  for(var i = 0; i<list.length; i++){
    getArtistAlbums(list[i], params)
      .then(function(data) {
        listCount++;
        for(var alb = 0; alb<data.length; alb++){
        //for(var alb = 0; alb<data.items.length; alb++){
          //albums.push(data.body.items[alb].id);
          albums.push(data[alb]);
        }
        // print out album list at the end of our loop
        if(listCount == list.length){
          console.log(albums);
        }

      }, function(err) {
        console.error(err);
      });
  }
  // prints out too early because of async nature of node.js
  //console.log(albums);
}

var listOfArtists = ['Aphex Twin', 'Metallica'];
var albums = [];

getAlbumsTotal(listOfArtists, 'dummy params');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接