nodejs使用异步调用循环遍历数组?

时间:2016-12-26 04:02:09

标签: javascript node.js

我正在尝试迭代一个将new Thing推送到列表的数组,在Thing中它会执行一些异步调用。我将如何以同步方式遍历数组,因为callback要求列表中的数据起作用。由于我的for循环是同步的并且进行了一些异步调用,因此如果完成了调用,则会在列表之前调用回调。

我不知道如何迭代数组并在进行回调之前完成所有工作

load(file, callback) {
  fs.readFile(file, (err, fd) => {
    var data = JSON.parse(fd);

    for(var i of data.array){
      this.list.push(new Thing(i.id)); // new Thing is Asynchronous
    }
    callback(); // Needs a finished list
    return;
  });
}

解决了它:

通过将我的Thing类转换为同步,通过删除对类中的函数的异步调用,并首先实例化循环中的所有Things然后调用Promise.all调用函数我解决了问题:< / p>

load(file, callback) {
  fs.readFile(file, (err, fd) => {
    var data = JSON.parse(fd);

    for(var i of data.array){
      this.list.push(new Thing(i.id));
    }

    Promise.all(this.list.map(i => i.load()).then(callback);
  });
}

3 个答案:

答案 0 :(得分:2)

你必须在Thing内部有一些状态来跟踪它的完成情况,例如你可以拥有一个承诺的实例变量。因此,考虑到Thing

这个被黑客攻击的例子
class Thing {
  constructor(id) {
    this.id = id;
    this.done = new Promise((resolve, reject) => {
      asyncThingWithCallback((err, data) {
        if (err) {
          this.asyncVal = null;
          reject(err);
        } else {
          this.asyncVal = data;
          resolve()
        }
      })
    });
  }
}

您可以在回调中使用done属性,如下所示:

load(file, callback) {
  fs.readFile(file, (err, fd) => {
    var data = JSON.parse(fd);

    for(var i of data.array){
      this.list.push(new Thing(i.id)); // new Thing is Asynchronous
    }

    Promise.all(this.list.map((thing) => thing.done))
      .then(callback)
  });
}

答案 1 :(得分:2)

首先,通常不建议使用需要一些异步操作来完成创建有效对象的构造函数。这不会导致易于编写或维护的代码,因为构造函数必须返回对象引用,并且因为操作是异步的,所以它必须在创建有效对象之前返回该对象引用。这只会导致凌乱的,部分创建的对象。您可以通过要求将完成回调传递给构造函数并确保调用代码在调用完成回调之后不尝试使用该对象来使其工作,但这不是一种干净的方法。它还使得异步操作不可能返回一个promise(这是异步设计的未来),因为构造函数必须返回对象引用,因此它不能返回一个promise。

您可以在对象中嵌入promise,但这也很麻烦,因为promise在初始异步操作期间才真正有用。

通常做的是使构造函数只是同步,然后使用.init()方法来执行异步部分。这样可以实现更清晰的代码,并且与使用promises的实现兼容。

或者,您可以创建一个工厂函数,该函数返回一个解析为对象引用的promise。

第二关,正如您已经知道的那样,您的for循环同步运行。在进入循环的下一部分之前,它不会“等待”其中的任何异步操作完成。只要循环的每次调用是独立的并且不依赖于先前的迭代,那就没关系了。您需要知道的是,当循环中的所有异步操作完成并使异步操作返回promise时,使用Promise.all()通常是最好的工具。

所以,假设你使用.init()方法方案,其中.init()执行初始化的异步部分,构造函数是同步的,.init()返回一个promise。然后,你可以这样做:

// create all the things objects
let things = data.array.map(i => new Thing(i.id)); 

// initialize them all asynchronously
Promise.all(things.map(item => { 
    return item.init();
})).then(function() {
    // all things are asynchronously initialized here
});

或者,使用返回解析为对象的promise的工厂函数的概念:

function newThing(i) {
    let o = new Thing(i.id);
    return o.init().then(function() {
        // resolve to the object itself
        return o;
    });
}

Promise.all(data.array.map(i => newThing(i))).then(things => {
    // all things in the array ready to be used here
});

如果你需要对数组迭代进行排序,那么第二次迭代就没有开始,直到第一次迭代的异步部分完成,第三次等待直到第二次迭代完成,等等,那么你就不能使用{{ 1}}循环,因为它根本不起作用。有几种不同的方法可以进行这种序列化的异步迭代。您可以在其他帖子中看到几种不同的方案:

How to synchronize a sequence of promises?

JavaScript: Perform a chain of promises synchronously

ES6 Promises - something like async.each?

How can I execute shell commands in sequence?

答案 2 :(得分:-1)

你可以使用primise.all在for循环之后运行所有的promise。然后你可以解析promise.all。

@IBAction func btnBack(_ sender: Any) {
    if (tag == 1) {
        webView.delegate = nil
        self.strUrl = ""
        webView.removeCache()
        let gotoCreateView = self.storyboard?.instantiateViewController(withIdentifier: "CreateAccountView") as! CreateAccountView

        self.present(gotoCreateView, animated: true, completion: nil)

    } else {

        webView.delegate = nil
        self.strUrl = ""
        webView.removeCache()
        let gotoAboutUsView = self.storyboard?.instantiateViewController(withIdentifier: "AboutUsView") as! AboutUsView

        self.present(gotoAboutUsView, animated: true, completion: nil)
    }
}