ES6 Promises - 类似 async.each 的东西?

Posted

技术标签:

【中文标题】ES6 Promises - 类似 async.each 的东西?【英文标题】:ES6 Promises - something like async.each? 【发布时间】:2015-11-08 19:13:43 【问题描述】:

试图弄清楚如何找到功能与 async.eachSeries 完全相同的东西,我需要一个按顺序运行(而不是并行)的异步操作列表,但在本机 ES6 中找不到方法,可以有人建议吗?

附言考虑过生成器/产量,但还没有经验,所以我没有意识到它到底能对我有多大帮助。

编辑 1

每个请求,这里是一个例子:

假设这段代码:

let model1 = new MongooseModel(prop1: "a", prop2: "b");
let model2 = new MongooseModel(prop1: "c", prop2: "d");

let arr = [model1 , model2];

现在,我想以一系列而不是并行的方式运行它,因此使用“异步”NPM 很容易:

async.eachSeries(arr, (model, next)=>
    model.save.then(next).catch(next);
, err=>
    if(err) return reject(error);
    resolve();
)

我的问题是:使用 ES6,我可以在本地实现吗?没有 NPM 'async' 包?

编辑 2

使用 async/await 可以轻松完成:

let model1 = new MongooseModel(prop1: "a", prop2: "b");
let model2 = new MongooseModel(prop1: "c", prop2: "d");    

let arr = [model1 , model2];

for(let model of arr)
    await model.save();

【问题讨论】:

你的意思是,第二个函数取决于第一个函数的结果? 不是必须依赖它,而是必须在前一个完成后运行。 您应该举一个例子并说明示例输入和预期输出,以正确解释问题。 加了个例子,谢谢! 【参考方案1】:

您可以通过在then 回调中返回来进行链接。例如:

new Promise(function(resolve, reject) 
  resolve(1)
).then(function(v)
  console.log(v);
  return v + 1;
).then(function(v)
  console.log(v)
);

将打印:

1 2

这在异步解析 Promise 时当然有效:

new Promise(function(resolve, reject)
  setTimeout(function()
    resolve(1);
  , 1000)
).then(function(result)
   return new Promise(function(resolve, reject)
     setTimeout(function()
       console.log(result);
       resolve(result + 1);
     , 1000)
   );
).then(function(results)
  console.log(results);
);

印刷:

1 2

【讨论】:

谢谢,但我需要它来动态收集数据,而不是一些已知的静态迭代量.. @ShlomiSasson 这只是一个例子。 then 所做的只是返回一个承诺,你传递回调并根据需要创建承诺。 Kit,所以你说的是创建一个递归函数,将越来越多的回调附加到“then”方法? @ShlomiSasson 好吧,您不必递归,就像 jfriend00 对 reduce 所做的那样。顺便说一句,有一个 ES7 提议让这个特殊情况更容易:github.com/lukehoban/ecmascript-asyncawait【参考方案2】:

假设您想对数据数组调用某个异步函数,并且希望它们按顺序调用,而不是并行调用。

async.eachSeries()的界面是这样的:

eachSeries(arr, iterator, [callback])

下面是如何用 Promise 模拟它:

// define helper function that works kind of like async.eachSeries
function eachSeries(arr, iteratorFn) 
    return arr.reduce(function(p, item) 
        return p.then(function() 
            return iteratorFn(item);
        );
    , Promise.resolve());

这假定iteratorFn 将要处理的项目作为参数并返回一个承诺。

这是一个用法示例(假设您有一个已承诺的 fs.readFileAsync())并有一个名为 speak() 的函数,该函数在完成时返回一个承诺:

 var files = ["hello.dat", "goodbye.dat", "genericgreeting.dat"];
 eachSeries(files, function(file) 
     return fs.readFileAsync(file).then(function(data) 
         return speak(data);
     );
 );

这让 Promise 基础架构可以为您排序所有内容。


您也可以手动排序(虽然我不确定为什么):

function eachSeries(arr, iteratorFn) 
    return new Promise(resolve, reject) 
        var index = 0;

        function next() 
            if (index < arr.length) 
                try 
                    iteratorFn(arr[index++]).then(next, reject);
                 catch(e) 
                    reject(e);
                
             else 
                resolve();
            
        
        // kick off first iteration
        next();
    );

或者,手动将 Promise 链接在一起的更简单的版本:

function eachSeries(arr, iteratorFn) 
    var index = 0;

    function next() 
        if (index < arr.length) 
            return iteratorFn(arr[index++]).then(next);
        
    
    return Promise.resolve().then(next);

请注意其中一个手动版本必须如何将iteratorFn()try/catch 括起来,以确保它是抛出安全的(将异常转换为拒绝)。 .then() 是自动抛出安全的,因此其他方案不必手动捕获异常,因为 .then() 已经为您捕获了它们。

【讨论】:

@Startec - .reduce() 接受两个参数。第一个是一个函数,第二个是你要减少的初始值。 Promise.resolve() 正在生成该初始值。然后使用两个参数调用您传递的回调。第一个参数是归约中的当前值。我在我的代码中将其命名为p,它的初始值将是Promise.resolve()。后续值将是我的回调 return p.then(...) 的返回值,这是另一个承诺。回调的第二个参数是我们要减少的数组中的下一个值。 @Startec - 有关.reduce() 的文档,请参阅here on MDN。你这样称呼它:var reducedValue = array.reduce(fn, initialValue)。我的Promise.resolve()initialValue。这只是一种通过将 Promise 链初始化为已解决的 Promise 来启动 Promise 链的方法。有点像Promise.resolve().then(fn1).then(fn2).then(fn3) @Startec - 当您考虑到正确的错误处理和潜在的嵌套操作时,promise 无疑是管理事物的最佳方式。此外,它们是 ES7 中 asyncawait 的核心,因此它们也是语言的未来。如果我想对数组进行异步排序,我会在 Bluebird Promise 库中使用 Promise.mapSeries() 之类的东西,这是我通常用来添加扩展 Promise 功能的库。 @Startec - 也许您应该发布一个问题,并详细说明您正在尝试做什么,并询问使用 Promises 的最佳方法是什么。我无法从您使用的单词中看出您的实际问题,因此最好查看您的代码,然后人们可以提供更好/不同的方法来做到这一点。我从未发现使用 Promise 比不使用 Promise 需要更多代码的情况。与许多事情一样,编写 Promise 代码有好方法,但不是那么好。 @DaveCausey - 是的,第一种方法提前创建了整个迭代长度的初始承诺链,然后让它运行。其他方法一次链接一个新的 Promise,因为前一个 Promise 解决了,旧的 Promise 可以被 GCed。如果您的迭代时间非常长(例如数百万次),那么后一种选项可能会使用明显较少的峰值内存量。【参考方案3】:

对于喜欢简短回答的人:

[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());

【讨论】:

第二个参数是减少的initial value。例如。这个特定的例子等价于Promise.resolve().then(func1).then(func2) 谢谢,当最后一个promise解决时,你会把代码放在哪里? @SSHThis 将.then(resultFromFunc2 =&gt; console.log("all done")) 放在最后,; 之前。 很有趣,虽然我不确定这与Promise.all([func1, func2]); 有何不同。 @ChadJohnson 异步函数按顺序运行,而不是并行运行。【参考方案4】:

//为运行较低nodejs版本(Azure :/)的系统上传这个 不是最短但我能想到的最好的

例如,让我们说“functionWithPromise”返回一些承诺并期望一些项目。

functionWithPromise(item);

promisesArray =[];

//syncornized
itemsArray.forEach(function (item)
   promisesArray.push(functionWithPromise(item));
);

Promise.all(promisesArray).then(function (values)
//profit
);

【讨论】:

这会并行运行 Promise,而不是按顺序运行【参考方案5】:

作为answer provided by @jib...的扩展,您还可以将一组项目映射到异步函数,如下所示:

[item1, item2]
    .map(item => async (prev_result) => await something_async(item))
    .reduce((p, f) => p.then(f), Promise.resolve())
    .then(() => console.log('all done'));

注意prev_result 将如何成为先前对something_async 的评估返回的值,这大致相当于async.eachSeriesasync.waterfall 之间的混合。

【讨论】:

以上是关于ES6 Promises - 类似 async.each 的东西?的主要内容,如果未能解决你的问题,请参考以下文章

ES6 Promises

带有 ES6 Promises 的 jQuery ajax

关于链接es6 Promises,然后()和价值消费

Promisifying xml2js 解析函数(ES6 Promises)

[Node.js] Testing ES6 Promises in Node.js using Mocha and Chai

使用 Promises 处理 Mongoose 错误