【问题标题】:How can I chain together groups of promises?如何将承诺组链接在一起?
【发布时间】:2014-08-16 16:19:18
【问题描述】:

我正在使用 Q javascript 承诺库并在浏览器中运行,我想弄清楚如何将承诺组链接在一起,以便每个组按顺序执行。例如,如果我有项目 A、B、C 和 D,我想将 A 和 B 组合在一起,然后将 C 和 D 组合在一起,这样 A 和 B 都必须在 C 和 D 执行之前完成。我创建了这个simple jsfiddle 来展示我当前的尝试。

var work_items = [ 'A','B','C','D','E','F','G','H','I' ];
var n = 2;    // group size
var wait = 1000;

var getWorkPromiseFn = function (item) {
    log("Getting promise function for " + item);
    return function () {
        log("Starting " + item);
        var deferred = Q.defer();
        setTimeout(function () {
            var status = "Finished " + item;
            log(status);
            deferred.resolve(status);             
        }, wait);
        return deferred.promise;
    };
};

var queue = Q();

//log('Getting sequentially');    // One-by-one in sequence works fine
//work_items.forEach(function (item) {
//    queue = queue.then(getWorkPromiseFn(item));
//});

log('Getting ' + n + ' at a time'); // This section does not        
while (work_items.length > 0) {
    var unit = [];
    for (var i=0; i<n; i++) {
        var item = work_items.shift();
        if (item) {
            unit.push(getWorkPromiseFn(item));               
        }
    }
    queue.then(Q.all(unit));
}
var inspect = queue.inspect(); // already fulfilled, though no work is done

看起来我可能在这里将错误的数组传递给Q.all,因为我传递的是一个返回承诺的函数数组,而不是承诺本身的数组。当我尝试直接在那里使用 Promise 时(例如 unit.push(Q().then(getWorkPromiseFn(item));),每个的工作都立即开始,没有顺序处理。我想我基本上不清楚以适当推迟小组执行的方式来代表小组的好方法。

那么,我怎样才能推迟执行一组这样的承诺呢?

【问题讨论】:

  • 你有什么进展吗?

标签: javascript promise q


【解决方案1】:

这可以通过首先将项目数组预处理成组,然后应用在“The Collection Kerfuffle”标题下提供的here 的两个模式(不是反模式)来完成。

主例程可以编码为单个数组方法链。

var work_items = [ 'A','B','C','D','E','F','G','H','I' ];
var wait = 3000;

//Async worker function
function getWorkPromise(item) {
    console.log("Starting " + item);
    var deferred = Q.defer();
    setTimeout(function () {
        var status = "Finished " + item;
        console.log(status);
        deferred.resolve(status);             
    }, wait);
    return deferred.promise;
};

function doAsyncStuffInGroups(arr, n) {
    /* 
     * Process original array into groups, then 
     * process the groups in series, 
     * progressing to the next group 
     * only after performing something asynchronous 
     * on all group members in parallel.
     */
    return arr.map(function(currentValue, i) {
        return (i % n === 0) ? arr.slice(i, i+n) : null;
    }).filter(function(item) {
        return item;
    }).reduce(function(promise, group) {
        return promise.then(function() {
            return Q.all(group.map(function(item) {
                return getWorkPromise(item);
            }));
        });
    }, Q());
}

doAsyncStuffInGroups(work_items, 2).then(function() {
    console.log("All done");
});

请参阅 fiddle。 3 秒的延迟让您有时间了解正在发生的事情。我发现 1s 太快了。

这样的解决方案优雅而简洁,但非常难以理解。在生产代码中,我会提供更多的 cmets 来帮助后来我的人。

记录在案:

  • 开头的arr.map(...).filter(...)arr (非破坏性地)处理成一个数组数组,每个内部数组代表一组长度为n(加上末端余数)。
  • 链接的.reduce(...) 是一个异步“序列化器”模式。
  • 嵌套的Q.all(group.map(...)) 是一种异步“并行器”模式。

【讨论】:

    【解决方案2】:

    promise 的 .then 函数不会改变 promise,所以当你这样做时:

     p.then(function(){
             // stuff
     });
    

    您根本不会更改承诺p,而是需要将其分配给某物:

    p = p.then(....)
    

    这就是为什么你的queue 承诺总是得到解决,它从未改变超过Q()

    在你的情况下,类似于改变:

    queue.then(Q.all(unit));
    

    进入:

    queue = queue.then(function(){ return Q.all(unit); });
    

    或者在 ES6 Promise 和库中使用它们的语法,如 Bluebird,提到的另一个答案:

    queue = queue.then(function(){ return Promise.all(unit); });
    

    【讨论】:

      【解决方案3】:

      最让我困惑的是,被链接的异步函数需要返回一个返回承诺的函数。这是一个例子:

      function setTimeoutPromise(ms) {
        return new Promise(function (resolve) {
          setTimeout(resolve, ms);
        });
      }
      
      function foo(item, ms) {
        return function() {
          return setTimeoutPromise(ms).then(function () {
            console.log(item);
          });
        };
      }
      
      var items = ['one', 'two', 'three'];
      
      function bar() {
        var chain = Promise.resolve();
        for (var i in items) {
          chain = chain.then(foo(items[i], (items.length - i)*1000));
        }
        return chain.then();
      }
      
      bar().then(function () {
        console.log('done');
      });
      

      注意 foo 返回一个返回承诺的函数。 foo() 确实直接返回一个承诺。

      看到这个Live Demo

      【讨论】:

        【解决方案4】:

        我建议你使用 bluebird,它的最佳性能承诺,https://github.com/petkaantonov/bluebird

        链接的例子也应该在这里https://github.com/petkaantonov/bluebird#how-do-long-stack-traces-differ-from-eg-q

        【讨论】:

        • 这不是真正的答案。
        • 很可能是蓝鸟的性能更好,但我不确定这是否相关。我相信我的基本困惑在任何常见的承诺库中都是相关的。无论如何,在 q github 存储库中有一些很好的链接示例。
        • 这不是答案,充其量只是评论。无论如何,答案是相当普遍的。
        猜你喜欢
        • 1970-01-01
        • 2017-01-14
        • 2019-05-24
        • 2016-12-30
        • 1970-01-01
        • 2013-11-25
        • 2020-03-19
        • 2018-02-11
        • 2015-04-15
        相关资源
        最近更新 更多