【问题标题】:Correct way to write loops for promise.为 promise 编写循环的正确方法。
【发布时间】:2016-11-08 01:51:54
【问题描述】:

如何正确地构造一个循环来确保后面的promise call和链式的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);

有什么建议吗?

【问题讨论】:

  • 代码对我来说看起来不错(使用loop 函数递归是执行同步循环的方法)。为什么你认为没有保证?
  • db.getUser(email) 保证按顺序调用。但是,由于 db.getUser() 本身是一个 Promise,因此按顺序调用它并不一定意味着由于 Promise 的异步特性,对“电子邮件”的数据库查询会按顺序运行。因此,logger.log(res) 的调用取决于哪个查询首先完成。
  • @user2127480:但循环的下一次迭代只有在 promise 解决后才会按顺序调用,这就是 while 代码的工作原理?

标签: javascript node.js promise bluebird


【解决方案1】:

使用 async 和 await (es6):

function taskAsync(paramets){
 return new Promise((reslove,reject)=>{
 //your logic after reslove(respoce) or reject(error)
})
}

async function fName(){
let arry=['list of items'];
  for(var i=0;i<arry.length;i++){
   let result=await(taskAsync('parameters'));
}

}

【讨论】:

    【解决方案2】:

    首先获取 promise 数组(promise 数组),然后使用 Promise.all(promisearray) 解析这些 promise 数组。

    var arry=['raju','ram','abdul','kruthika'];
    
    var promiseArry=[];
    for(var i=0;i<arry.length;i++) {
      promiseArry.push(dbFechFun(arry[i]));
    }
    
    Promise.all(promiseArry)
      .then((result) => {
        console.log(result);
      })
      .catch((error) => {
         console.log(error);
      });
    
    function dbFetchFun(name) {
      // we need to return a  promise
      return db.find({name:name}); // any db operation we can write hear
    }
    

    【讨论】:

      【解决方案3】:

      使用标准的 promise 对象,并让 promise 返回结果。

      function promiseMap (data, f) {
        const reducer = (promise, x) =>
          promise.then(acc => f(x).then(y => acc.push(y) && acc))
        return data.reduce(reducer, Promise.resolve([]))
      }
      
      var emails = []
      
      function getUser(email) {
        return db.getUser(email)
      }
      
      promiseMap(emails, getUser).then(emails => {
        console.log(emails)
      })
      

      【讨论】:

        【解决方案4】:

        有一种新方法可以解决这个问题,那就是使用 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

        【讨论】:

        • 谢谢,这不涉及使用框架(蓝鸟)。
        【解决方案5】:

        如果您真的想要一个通用的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');
        });
        

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

        【讨论】:

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

        给定

        • 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())
        

        【讨论】:

        • 如果 async 即将成为 JavaScript 中的保留字,它可能会在此处重命名该函数以增加清晰度。
        • 另外,在大括号中没有主体的胖箭头函数不是简单地返回表达式的计算结果吗?这将使代码更简洁。我还可以添加一条评论,说明 current 未使用。
        • 这才是正道!
        【解决方案7】:

        这是另一种方法(ES6 w/std Promise)。使用 lodash/underscore 类型的退出条件(return === false)。请注意,您可以轻松地在选项中添加 exitIf() 方法以在 doOne() 中运行。

        const whilePromise = (fnReturningPromise,options = {}) => { 
            // loop until fnReturningPromise() === false
            // options.delay - setTimeout ms (set to 0 for 1 tick to make non-blocking)
            return new Promise((resolve,reject) => {
                const doOne = () => {
                    fnReturningPromise()
                    .then((...args) => {
                        if (args.length && args[0] === false) {
                            resolve(...args);
                        } else {
                            iterate();
                        }
                    })
                };
                const iterate = () => {
                    if (options.delay !== undefined) {
                        setTimeout(doOne,options.delay);
                    } else {
                        doOne();
                    }
                }
                Promise.resolve()
                .then(iterate)
                .catch(reject)
            })
        };
        

        【讨论】:

          【解决方案8】:

          我会做这样的:

          var request = []
          while(count<10){
             request.push(db.getUser(email).then(function(res) { return res; }));
             count++
          };
          
          Promise.all(request).then((dataAll)=>{
            for (var i = 0; i < dataAll.length; i++) {
          
                logger.log(dataAll[i]); 
            }  
          });
          

          这样,dataAll 是要记录的所有元素的有序数组。当所有的promise都完成后,日志操作就会执行。

          【讨论】:

          • Promise.all 会同时调用 will call Promise。所以完成的顺序可能会改变。这个问题要求链式承诺。所以完成的顺序不应该改变。
          • 编辑 1:您根本不需要调用 Promise.all。只要 promise 被触发,它们就会被并行执行。
          【解决方案9】:

          这个使用BlueBird怎么样?

          function fetchUserDetails(arr) {
              return Promise.each(arr, function(email) {
                  return db.getUser(email).done(function(res) {
                      logger.log(res);
                  });
              });
          }
          

          【讨论】:

            【解决方案10】:
            function promiseLoop(promiseFunc, paramsGetter, conditionChecker, eachFunc, delay) {
                function callNext() {
                    return promiseFunc.apply(null, paramsGetter())
                        .then(eachFunc)
                }
            
                function loop(promise, fn) {
                    if (delay) {
                        return new Promise(function(resolve) {
                            setTimeout(function() {
                                resolve();
                            }, delay);
                        })
                            .then(function() {
                                return promise
                                    .then(fn)
                                    .then(function(condition) {
                                        if (!condition) {
                                            return true;
                                        }
                                        return loop(callNext(), fn)
                                    })
                            });
                    }
                    return promise
                        .then(fn)
                        .then(function(condition) {
                            if (!condition) {
                                return true;
                            }
                            return loop(callNext(), fn)
                        })
                }
            
                return loop(callNext(), conditionChecker);
            }
            
            
            function makeRequest(param) {
                return new Promise(function(resolve, reject) {
                    var req = https.request(function(res) {
                        var data = '';
                        res.on('data', function (chunk) {
                            data += chunk;
                        });
                        res.on('end', function () {
                            resolve(data);
                        });
                    });
                    req.on('error', function(e) {
                        reject(e);
                    });
                    req.write(param);
                    req.end();
                })
            }
            
            function getSomething() {
                var param = 0;
            
                var limit = 10;
            
                var results = [];
            
                function paramGetter() {
                    return [param];
                }
                function conditionChecker() {
                    return param <= limit;
                }
                function callback(result) {
                    results.push(result);
                    param++;
                }
            
                return promiseLoop(makeRequest, paramGetter, conditionChecker, callback)
                    .then(function() {
                        return results;
                    });
            }
            
            getSomething().then(function(res) {
                console.log('results', res);
            }).catch(function(err) {
                console.log('some error along the way', err);
            });
            

            【讨论】:

              【解决方案11】:

              Bergi 推荐的功能真的很不错:

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

              在使用 Promise 时,我仍然想做一个有意义的小补充:

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

              这样,while 循环可以嵌入到 promise 链中并使用 lastValue 解析(如果 action() 从未运行)。见例子:

              var count = 10;
              util.promiseWhile(
                function condition() {
                  return count > 0;
                },
                function action() {
                  return new Promise(function(resolve, reject) {
                    count = count - 1;
                    resolve(count)
                  })
                },
                count)
              

              【讨论】:

                【解决方案12】:

                这是我使用标准 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
                • 这种方式如何发送参数?
                • @khan 在 chain = chain.then(func) 行,你可以这样做:chain = chain.then(func.bind(null, "...your params here"));chain = chain.then(() =&gt; func("your params here"));
                【解决方案13】:

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

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

                有什么建议吗?

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

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

                【讨论】:

                • 糟糕。除了actionpromiseFor 中将value 作为其参数。所以不会让我做这么小的编辑。谢谢,它非常有用且优雅。
                • @Roamer-1888:也许术语有点奇怪,但我的意思是while 循环确实测试了一些全局状态,而for 循环的迭代变量(计数器)绑定到循环体本身。事实上,我使用了一种更实用的方法,它看起来更像是定点迭代而不是循环。再次检查他们的代码,value 参数不同。
                • 好的,我现在看到了。由于.bind() 混淆了新的value,我想我可能会选择速写函数以提高可读性。对不起,如果我很厚,但如果 promiseForpromiseWhile 不共存,那么如何称呼另一个?
                • @herve 你基本上可以省略它并将return …替换为return Promise.resolve(…)。如果您需要针对conditionaction 抛出异常(如Promise.method provides it)的额外保护措施,请将整个函数体包装在return Promise.resolve().then(() =&gt; { … })
                • @herve 其实应该是Promise.resolve().then(action).…或者Promise.resolve(action()).…then的返回值不用换行
                猜你喜欢
                • 1970-01-01
                • 2015-09-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-03-01
                • 2020-04-24
                • 1970-01-01
                相关资源
                最近更新 更多