简体   繁体   English

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

[英]nodejs looping through array with async calls?

I am trying to iterate through an array which pushes a new Thing to a list, inside the Thing it does some async calls of its own. 我试图遍历一个数组,它将一个new Thing推送到一个列表,在Thing内部它会做一些自己的异步调用。 How would I iterate through an array in a synchronous way, as the callback requires the the data from the list to work. 我将如何以同步方式迭代数组,因为callback需要列表中的数据才能工作。 Since my for loop is synchronous and does some asynchronous calls, the callback is called before the the list if finished being made. 由于我的for循环是同步的并且进行了一些异步调用,因此如果完成了调用,则会在列表之前调用回调。

I do not know how I would iterate through the array and do all the work before doing the callback 我不知道如何迭代数组并在进行回调之前完成所有工作

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;
  });
}

Solved it: 解决了它:

By converting my Thing class to synchronous, by removing the asynchronous calls to a function inside the class, and first instantiating all the Things in the loop then calling Promise.all calling the function I solved the issue: 通过将我的Thing类转换为同步,通过删除对类中的函数的异步调用,并首先实例化循环中的所有Things然后调用Promise.all调用函数我解决了问题:

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);
  });
}

You'd have to have some state inside of Thing to track it's doneness for example you could have an instance variable that's a promise. 你必须在Thing内部有一些状态来跟踪它的完成情况,例如你可以拥有一个承诺的实例变量。 So given this hacked together example of 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()
        }
      })
    });
  }
}

You can use the done property inside of your callback like this: 您可以在回调中使用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)
  });
}

First off, it's generally not advisable to have a constructor that needs some asynchronous operation to finish creating a valid object. 首先,通常不建议使用需要一些异步操作来完成创建有效对象的构造函数。 That just doesn't lead to easily writable or maintainable code because the constructor has to return the object reference and because the operation is asynchronous, it has to return that object reference before you're done creating a valid object. 这不会导致易于编写或维护的代码,因为构造函数必须返回对象引用,并且因为操作是异步的,所以它必须在创建有效对象之前返回该对象引用。 That just leads to messy, partially created objects. 这只会导致凌乱的,部分创建的对象。 You can make it work by requiring a completion callback be passed to the constructor and making sure the calling code does not attempt to use the object until after the completion callback has been called, but this is just not a clean way to do things. 您可以通过要求将完成回调传递给构造函数并确保调用代码在调用完成回调之后不尝试使用该对象来使其工作,但这不是一种干净的方法。 It also makes it impossible to have your async operation return a promise (which is the future of async design) because the constructor has to return the object reference so it can't return a promise. 它还使得异步操作不可能返回一个promise(这是异步设计的未来),因为构造函数必须返回对象引用,因此它不能返回一个promise。

You could embed the promise in the object, but that's messy too because the promise is only really useful during the initial async operation. 您可以在对象中嵌入promise,但这也很麻烦,因为promise在初始异步操作期间才真正有用。

What is often done instead is to make the constructor be only synchronous and then have a .init() method that does the async parts. 通常做的是使构造函数只是同步,然后有一个.init()方法来执行异步部分。 That makes for cleaner code and is compatible with implementations using promises. 这使得代码更清晰,并且与使用promises的实现兼容。

Or, you can create a factory function that returns a promise that resolves to the object reference. 或者,您可以创建一个工厂函数,该函数返回一个解析为对象引用的promise。

Second off, as you already seem to know, your for loop runs synchronously. 第二关,正如您已经知道的那样,您的for循环同步运行。 It doesn't "wait" for any async operations inside it to complete before going onto the next part of the loop. 在进入循环的下一部分之前,它不会“等待”其中的任何异步操作完成。 As long as each invocation of the loop is separate and doesn't depend upon the prior iteration, that's all fine. 只要循环的每次调用是独立的并且不依赖于先前的迭代,那就没关系了。 All you need to know is when all the async operations in the loop are done and making your async operations return promises and using Promise.all() is generally the best tool for that. 所有你需要知道的是当循环中的所有异步操作完成并使异步操作返回promises并且使用Promise.all()通常是最好的工具。

So, let's supposed you use the .init() method scheme where .init() does the async part of the initialization and the constructor is synchronous and .init() returns a promise. 所以,假设您使用.init()方法方案,其中.init()执行初始化的异步部分,构造函数是同步的,而.init()返回一个promise。 Then, you could do this: 然后,你可以这样做:

// 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
});

Or, using the concept of a factory function that returns a promise that resolves to the object: 或者,使用工厂函数的概念,该函数返回解析为对象的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
});

If you need to sequence your array iteration so the 2nd iteration did not start until the async part of the first iteration was done and 3rd waited until the 2nd iteration was done and so on, then you can't use a for loop because it simply doesn't work that way. 如果你需要对你的数组迭代进行排序,那么第二次迭代就没有开始,直到第一次迭代的异步部分完成,第三次等待直到第二次迭代完成,等等,那么你就不能使用for循环,因为它只是不这样做。 There are several different ways to do such a serialized async iteration. 有几种不同的方法可以进行这种序列化的异步迭代。 You can see several different schemes in these other posts: 您可以在这些其他帖子中看到几种不同的方案:

How to synchronize a sequence of promises? 如何同步一系列承诺?

JavaScript: Perform a chain of promises synchronously JavaScript:同步执行一系列承诺

ES6 Promises - something like async.each? ES6 Promises - 像async.each这样的东西?

How can I execute shell commands in sequence? 如何按顺序执行shell命令?

You can use primise.all to run all the promises after the for loop .Then you can resolve the promise.all . 您可以使用primise.all在for循环之后运行所有promise。然后您可以解析promise.all。

load(file) {
 fs.readFile(file).Then(function (fd){
var data = JSON.parse(fd);
var EachPromise = [ ]; 
for(var i of data.array){
EachPromise.push(new Thing(i.id)); // new Thing is Asynchronous
}
Promise.all(EachPromise)  .then(function (result){
console.log('this is result',result);  
}).Catch(function (error){
console.log('this is error', error);
});
}

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

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