为承诺编写循环的正确方法。

2022-08-30 05:05:12

如何正确构造一个循环来确保以下 promise 调用和链接的记录器.log(res) 在迭代中同步运行?(蓝鸟)

db.getUser(email).then(function(res) { logger.log(res); }); // this is a promise

我尝试了以下方法(来自 http://blog.victorquinn.com/javascript-promise-while-loop )

var Promise = require('bluebird');

var promiseWhile = function(condition, action) {
    var resolver = Promise.defer();

    var loop = function() {
        if (!condition()) return resolver.resolve();
        return Promise.cast(action())
            .then(loop)
            .catch(resolver.reject);
    };

    process.nextTick(loop);

    return resolver.promise;
});

var count = 0;
promiseWhile(function() {
    return count < 10;
}, function() {
    return new Promise(function(resolve, reject) {
        db.getUser(email)
          .then(function(res) { 
              logger.log(res); 
              count++;
              resolve();
          });
    }); 
}).then(function() {
    console.log('all done');
}); 

虽然它似乎有效,但我认为它不能保证调用logger的顺序.log(res);

有什么建议吗?


答案 1

如果你真的想要一个用于这个和其他目的的通用函数,那么一定要使用Bergi的简化来做到这一点。但是,由于 promise 的工作方式,以这种方式传递回调通常是不必要的,并且会迫使您跳过复杂的小箍。promiseWhen()

据我所知,您正在尝试:

  • 异步获取电子邮件地址集合的一系列用户详细信息(至少,这是唯一有意义的方案)。
  • 通过递归构建链来做到这一点。.then()
  • 以在处理返回的结果时保持原始顺序。

因此,这个问题实际上是在Promise Anti-patterns的“The Collection Kerfuffle”下讨论的问题,它提供了两个简单的解决方案:

  • 并行异步调用使用Array.prototype.map()
  • 使用 的串行异步调用。Array.prototype.reduce()

并行方法将(直接)给出您试图避免的问题 - 响应的顺序不确定。串行方法将构建所需的链 - 平面 - 无递归。.then()

function fetchUserDetails(arr) {
    return arr.reduce(function(promise, email) {
        return promise.then(function() {
            return db.getUser(email).done(function(res) {
                logger.log(res);
            });
        });
    }, Promise.resolve());
}

呼叫如下:

//Compose here, by whatever means, an array of email addresses.
var arrayOfEmailAddys = [...];

fetchUserDetails(arrayOfEmailAddys).then(function() {
    console.log('all done');
});

如您所见,不需要丑陋的外部 var 或其关联函数。极限(在问题中为 10)完全由数组的长度决定。countconditionarrayOfEmailAddys


答案 2

我不认为它保证了调用logger的顺序.log(res);

实际上,确实如此。该语句在调用之前执行。resolve

有什么建议吗?

很多。最重要的是你使用创建承诺手动反模式 - 只需做

promiseWhile(…, function() {
    return db.getUser(email)
             .then(function(res) { 
                 logger.log(res); 
                 count++;
             });
})…

其次,该功能可以简化很多:while

var promiseWhile = Promise.method(function(condition, action) {
    if (!condition()) return;
    return action().then(promiseWhile.bind(null, condition, action));
});

第三,我不会使用循环(带有闭包变量),而是使用循环:whilefor

var promiseFor = Promise.method(function(condition, action, value) {
    if (!condition(value)) return value;
    return action(value).then(promiseFor.bind(null, condition, action));
});

promiseFor(function(count) {
    return count < 10;
}, function(count) {
    return db.getUser(email)
             .then(function(res) { 
                 logger.log(res); 
                 return ++count;
             });
}, 0).then(console.log.bind(console, 'all done'));