循环遍历任务瀑布- promises bluebird

Loop through tasks waterfall- promises bluebird

我希望使用 bluebird 循环执行一些任务,只是将超时用作实验机制。 [不打算使用异步或任何其他库]

var Promise = require('bluebird');

var fileA = {
    1: 'one',
    2: 'two',
    3: 'three',
    4: 'four',
    5: 'five'
};


function calculate(key) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve(fileA[key]);
        }, 500);
    });
}

Promise.map(Object.keys(fileA), function (key) {
    calculate(key).then(function (res) {
        console.log(res);
    });
}).then(function () {
    console.log('finish');
});

结果是

finish,
one,
two,
three,
four,
five,

我需要循环在每次超时完成时只迭代一次,然后在完成时触发最后一个 thenable。

  1. 在传递给Promise.map的函数对象中,需要return一个Promise对象,这样所有的Promises都会被resolved,并且resolved值的数组可以传递给下一个 then 函数。在您的情况下,由于您没有明确 returning 任何内容,因此默认情况下 undefined 将被 returned,而不是承诺。因此,finish 的 thenable 函数被执行,因为 Promises.map 的 Promise 被 undefined 解决了。你可以这样确认

    ...
    }).then(function (result) {
        console.log(result);
        console.log('finish');
    });
    

    会打印

    [ undefined, undefined, undefined, undefined, undefined ]
    finish
    one
    two
    three
    four
    five
    

    所以,您的代码应该有这样的 return 语句

    Promise.map(Object.keys(fileA), function (key) {
        return calculate(key).then(function (res) {
            console.log(res);
        });
    }).then(function () {
        console.log('finish');
    });
    

    现在,您将看到代码按顺序打印事物,因为我们 return Promise 对象和带有 finish 的 thenable 函数在所有 Promise 被解决后被调用。但它们都没有按顺序解决。如果发生这种情况,每个数字都会在指定的时间过去后打印出来。这就把我们带到了第二部分。

  2. Promise.map 将执行作为参数传递的函数,一旦数组中的 Promises 被解析。引用文档,

    The mapper function for a given item is called as soon as possible, that is, when the promise for that item's index in the input array is fulfilled.

    因此,数组中的所有值都被转换为 Promise,并使用相应的值进行解析,并且将立即为每个值调用该函数。因此,他们都同时等待 500 毫秒并立即解决。这不会按顺序发生。

    既然要它们顺序执行,就需要用到Promise.each。引用文档,

    Iteration happens serially. .... If the iterator function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration.

    由于 Promise 是连续创建的,并且在继续之前等待解决,因此结果的顺序是有保证的。所以你的代码应该变成

    Promise.each(Object.keys(fileA), function (key) {
        return calculate(key).then(function (res) {
            console.log(res);
        });
    }).then(function () {
        console.log('finish');
    });
    

    补充说明:

    如果顺序无关紧要,正如 Benjamin Gruenbaum 所建议的,您可以使用 Promise.map 本身和 concurrency limit,像这样

    Promise.map(Object.keys(fileA), function (key) {
        return calculate(key).then(function (res) {
            console.log(res);
        });
    }, { concurrency: 1 }).then(function () {
        console.log('finish');
    });
    

    concurrency 选项基本上限制了在创建更多承诺之前可以创建和解决的承诺数量。因此,在这种情况下,由于限制为 1,它将创建第一个承诺,并且当达到限制时,它将等到创建的 Promise 解决,然后再继续下一个 Promise。


如果使用calculate的全部目的是引入延迟,那么我会推荐Promise.delay,可以这样使用

Promise.each(Object.keys(fileA), function (key) {
    return Promise.delay(500).then(function () {
        console.log(fileA[key]);
    });
}).then(function () {
    console.log('finish');
});

delay 可以透明地将 Promise 的 resolved 值链接到下一个 thenable 函数,因此代码可以缩短为

Promise.each(Object.keys(fileA), function (key) {
    return Promise.resolve(fileA[key]).delay(500).then(console.log);
}).then(function () {
    console.log('finish');
});

由于Promise.delay接受一个动态值,你可以简单地写成

Promise.each(Object.keys(fileA), function (key) {
    return Promise.delay(fileA[key], 500).then(console.log);
}).then(function () {
    console.log('finish');
});

如果 Promise 链自己在这里结束,最好使用 .done() 方法来标记它,像这样

...
}).done(function () {
    console.log('finish');
});

一般注意事项:如果您不打算在可用函数中进行任何处理,而只是使用它来跟踪进度或遵循流程,那么您可以最好将它们更改为 Promise.tap。所以,你的代码会变成

Promise.each(Object.keys(fileA), function (key) {
    return Promise.delay(fileA[key], 500).tap(console.log);
}).then(function () {
    // Do processing
    console.log('finish');
});