为 promise 编写循环的正确方法。

IT技术 javascript node.js promise bluebird
2021-01-26 19:44:41

如何正确构造循环以确保以下Promise调用和链接的logger.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);的顺序

有什么建议?

6个回答

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

据我所知,您正在尝试:

  • 异步获取一组电子邮件地址的一系列用户详细信息(至少,这是唯一有意义的情况)。
  • .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');
});

如您所见,不需要丑陋的外部 varcount或其关联condition函数。限制(问题中的 10 个)完全由数组的长度决定arrayOfEmailAddys

@wayofthefuture,不。这样想……你不能改变历史。
2021-03-14 19:44:41
感觉这应该是选定的答案。优雅且可重用的方法。
2021-03-18 19:44:41
@Roamer-1888 我的错误,我误读了最初的问题。我(个人)正在研究一个解决方案,在该解决方案中,随着您的请求解决(它是数据库的查询更多),您需要减少的初始列表正在增长。在这种情况下,我发现将 reduce 与生成器一起使用的想法很好地分离了(1)promise 链的条件扩展和(2)返回结果的消耗。
2021-03-25 19:44:41
有谁知道捕获是否会传播回父级?例如,如果 db.getUser 失败,(拒绝)错误会传播回来吗?
2021-04-07 19:44:41
谢谢你的回答。这应该是公认的答案。
2021-04-09 19:44:41

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

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

有什么建议?

很多。最重要的是您使用create-promise-manually 反模式- 只做

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));
});

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

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'));
oop。不同之处在于action采用value作为其参数promiseFor所以不会让我做这么小的编辑。谢谢,它非常有帮助和优雅。
2021-03-12 19:44:41
好的,我现在看到了。由于.bind()混淆了 new value,我想我可能会选择将函数写出来以提高可读性。对不起,如果我很厚,但如果promiseForpromiseWhile不共存,那么一个人如何称呼另一个人?
2021-03-24 19:44:41
@herve 您基本上可以省略它并替换return …by return Promise.resolve(…)如果您需要额外的保护措施来防止conditionaction抛出异常(如Promise.method提供),请将整个函数体包装在一个return Promise.resolve().then(() => { … })
2021-04-02 19:44:41
@herve 实际上应该是Promise.resolve().then(action).…or Promise.resolve(action()).…,您不需要包装的返回值then
2021-04-02 19:44:41
@Roamer-1888:也许这个术语有点奇怪,但我的意思是while循环确实测试了一些全局状态,而for循环的迭代变量(计数器)绑定到循环体本身。事实上,我使用了一种功能更强大的方法,它看起来更像是一个固定点迭代而不是一个循环。再次检查他们的代码,value参数不同。
2021-04-06 19:44:41

这是我如何使用标准的 Promise 对象。

// Given async function sayHi
function sayHi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Hi');
      resolve();
    }, 3000);
  });
}

// And an array of async functions to loop through
const asyncArray = [sayHi, sayHi, sayHi];

// We create the start of a promise chain
let chain = Promise.resolve();

// And append each function in the array to the promise chain
for (const func of asyncArray) {
  chain = chain.then(func);
}

// Output:
// Hi
// Hi (After 3 seconds)
// Hi (After 3 more seconds)
很好的答案@youngwerth
2021-03-24 19:44:41
@khan 在 chain = chain.then(func) 行上,您可以执行以下任一操作:chain = chain.then(func.bind(null, "...your params here"));chain = chain.then(() => func("your params here"));
2021-04-05 19:44:41
如何以这种方式发送参数?
2021-04-10 19:44:41

给定的

  • asyncFn 函数
  • 项目数组

必需的

  • Promise链接 .then() 的串联(按顺序)
  • 原生es6

解决方案

let asyncFn = (item) => {
  return new Promise((resolve, reject) => {
    setTimeout( () => {console.log(item); resolve(true)}, 1000 )
  })
}

// asyncFn('a')
// .then(()=>{return async('b')})
// .then(()=>{return async('c')})
// .then(()=>{return async('d')})

let a = ['a','b','c','d']

a.reduce((previous, current, index, array) => {
  return previous                                    // initiates the promise chain
  .then(()=>{return asyncFn(array[index])})      //adds .then() promise for each item
}, Promise.resolve())
此外,在没有大括号中的主体的情况下,胖箭头函数不是简单地返回那里的表达式的计算结果吗?这将使代码更加简洁。我还可以添加一条评论,说明current未使用。
2021-03-27 19:44:41
如果async即将成为 JavaScript 中的保留字,在这里重命名该函数可能会增加清晰度。
2021-04-05 19:44:41
这是正确的方法!
2021-04-05 19:44:41

有一种新方法可以解决这个问题,那就是使用 async/await。

async function myFunction() {
  while(/* my condition */) {
    const res = await db.getUser(email);
    logger.log(res);
  }
}

myFunction().then(() => {
  /* do other stuff */
})

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function https://ponyfoo.com/articles/understanding-javascript-async-await

谢谢,这不涉及使用框架(bluebird)。
2021-03-28 19:44:41