Promise 在 forEach 循环完成之前解决

Posted

技术标签:

【中文标题】Promise 在 forEach 循环完成之前解决【英文标题】:Promise resolves before forEach loop completes 【发布时间】:2018-04-28 03:50:57 【问题描述】:

我正在构建我的第一个 CRUD(库)应用程序,并且最近了解了将 Promise 作为一种避免深度嵌套回调的方法。每次服务器启动时,我都试图用一些数据为我的数据库播种,但我似乎在概念上遗漏了一些东西。

我在 bookData 数组中有四个对象,我想使用 Mongoose 对其进行迭代并保存到数据库:

function seedBooks() 
    return new Promise(function(resolve,reject)
        bookData.forEach(function(seed)
            Book.create(seed, function(err, newBook)
                if(err) 
                    reject(err);
                
            );
        );
        resolve();
    );

这个函数是我试图链接在一起的几个函数之一,这就是我使用 Promise 的原因。但我发现seedBooks() 可以解决 1 到 4 个样本书的创建问题,

function seedDB() 
    removeAllBooks()
    .then(function()
        return removeAllUsers();
    )
    .then(function()
        return seedUsers();
    )
    .then(function()
        return seedBooks();
    )
    .then(function()
        return seedBookInstances();
    );

我是否理解或使用了 promise & resolve 不正确?任何帮助表示赞赏。谢谢!

【问题讨论】:

【参考方案1】:

您正在同步解决您的 Promise,就在您启动您的 forEached 请求之后,这些请求是异步的。您可以尝试以下方法:

function seedBooks() 
    return new Promise(function(resolve,reject)
        var count = 0, length = bookData.length;
        bookData.forEach(function(seed)
            Book.create(seed, function(err, newBook)
                if(err) 
                    reject(err);
                    return;
                
                if(count++ >= length ) 
                  resolve();
                
            );
        );
    );

这里的 Promise 只有在所有异步请求都完成后才会被解析。

另一种选择是使用Promise.all。在这种方法中,您需要在循环中承诺所有请求,返回一个 Promises 数组,然后调用 Promise.all(_seedBooks()).then(),其中 _seedBook 返回一个 Promises 数组:

function _seedBooks() 
    return bookData.map(function(seed) 
        return new Promise(function(resolve, reject) 
            Book.create(seed, function(err, newBook) 
                if(err) 
                    reject(err);
                    return;
                
                resolve(newBook);
            );
        );
    );


Promise.all(_seedBooks())
.then(function(result)  /* result is the array of newBook objects */ )
.catch(function(error)  /* error is the first rejected err */ )

【讨论】:

【参考方案2】:

编辑: 下面解释了为什么您的代码无法正常工作,并就如何将 non-promise 代码转换为 Promise 提供一般建议。但是,由于 Mongoose 会产生 Promise,因此您应该使用它们而不是使用 new Promise。请参阅Olegzandr's answer。


承诺会立即解决,因为您正在立即调用resolve()

将非承诺转换为承诺时的经验法则是承诺非承诺代码的最小部分。在这种情况下,这意味着承诺代码以保存单个项目。如果你这样做,呼叫resolve() 的收集地点就会变得清晰:

function seedBook(seed) 
    return new Promise(function (resolve, reject) 
        Book.create(seed, function (err, newBook) 
            if (err)  reject(err);  else  resolve(newBook); 
        );
    );


function seedBooks() 
    return Promise.all(bookData.map(seedBook));

如果您愿意,这还有一个好处是允许您访问返回的newBooks。

【讨论】:

【参考方案3】:

如果您使用的是 Mongoose,您可以这样做:

const saveBooks = function(books) 
    return books.map(function(seed) 
            return Book.create(seed);  // returns a promise
        );
    );


return Promise.all(saveBooks(books)).then(function()
    // all books are saved
);

【讨论】:

以上是关于Promise 在 forEach 循环完成之前解决的主要内容,如果未能解决你的问题,请参考以下文章

在嵌套 forEach 中的 Promise.all 之前评估 Promise,导致 Promise.all 为空

在 forEach 循环完成后运行回调函数

Javascript等待Promise在返回响应之前完成for循环

在 forEach 循环中等待 promise

forEach 循环中的 promise.all —— 一切立即触发

如何在 forEach 循环节点中使用 Promise?