正确编写承诺循环的方式。


116

如何正确构造一个循环,以确保以下的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);的顺序

有什么建议?


1
代码对我来说看起来不错(使用loop函数的递归是进行同步循环的方式)。您为什么认为没有保证?
hugomg 2014年

保证db.getUser(email)被依次调用。但是,由于db.getUser()本身是一个Promise,所以由于Promise的异步功能,按顺序调用它不一定意味着数据库查询“电子邮件”将按顺序运行。因此,将根据哪个查询恰好首先完成来调用logger.log(res)。
user2127480 2014年

1
@ user2127480:但是循环的下一次迭代只有在promise解决后才被顺序调用,这就是while代码的工作原理吗?
Bergi 2014年

Answers:


78

我不认为这可以保证调用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));
});

第三,我不会使用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'));

2
哎呀 不同之处在于action采用value作为其参数promiseFor。所以我不会做这么小的编辑。谢谢,它非常有用且优雅。
Gordon

1
@ Roamer-1888:也许术语有点奇怪,但是我的意思是,while循环确实测试某些全局状态,而for循环将其迭代变量(计数器)绑定到循环主体本身。实际上,我使用了一种更具功能性的方法,它看起来更像是定点迭代而不是循环。再次检查他们的代码,value参数不同。
Bergi 2014年

2
好的,我现在看到了。由于.bind()模糊了新功能value,我认为我可能会选择长期使用该功能以提高可读性。很遗憾,如果我是厚,但如果promiseForpromiseWhile不共存,那么如何做一个呼叫中的其他?
Roamer-1888

2
@herve基本上可以忽略它并更换return …return Promise.resolve(…)。如果您需要其他防范措施conditionaction引发异常(如Promise.method提供异常),请将整个函数体包装在return Promise.resolve().then(() => { … })
Bergi

2
@herve实际上应该为Promise.resolve().then(action).…Promise.resolve(action()).…,您无需包装then
Bergi

134

如果您真的想要一个通用的promiseWhen()功能用于此目的和其他目的,那么一定要使用Bergi的简化方法。但是,由于promise的工作方式,通常不需要以这种方式传递回调,这会迫使您跳过复杂的小圈子。

据我所知,您正在尝试:

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

这样定义的问题实际上是Promise Anti-patterns中的 “集合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


16
感觉应该是选择的答案。优美且可重复使用的方法。
2016年

1
有谁知道渔获物是否会传播回父母?例如,如果db.getUser失败,(拒绝)错误会传播回去吗?
wayofthefuture

@wayofthefuture,不。这样想吧.....您无法更改历史记录。
Roamer-1888

4
感谢你的回答。这应该是公认的答案。
klvs

1
@ Roamer-1888我的错,我看错了原始问题。我(个人)正在研究一个解决方案,其中您需要减少的初始清单随着请求的解决而增长(它的查询更多于数据库)。在这种情况下,我发现可以通过生成器使用reduce来实现以下目的:(1)promise链的条件扩展和(2)返回的结果的消耗。
jhp

40

这是我使用标准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
Jam Risser

3
如何以这种方式发送参数?
阿卡什·汗

4
@khan在chain = chain.then(func)行上,您可以执行以下任一操作:chain = chain.then(func.bind(null, "...your params here"));chain = chain.then(() => func("your params here"));
youngwerth,2016年

9

给定

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

2
如果async要在JavaScript中成为保留字,则可以在此处重新命名该功能,以增加清晰度。
hippietrail

此外,如果没有大括号的主体使用粗箭头功能,是否仅返回表达式的计算结果呢?这将使代码更简洁。我可能还会添加一条说明current未使用的评论。
hippietrail

2
这是正确的方法!
teleme.io


3

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)

3

我会做这样的事情:

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.all将同时调用will call promises。因此,完成顺序可能会更改。这个问题要求连锁承诺。因此,完成顺序不应更改。
canbax

编辑1:根本不需要调用Promise.all。只要承诺被兑现,它们就会并行执行。
canbax

1

使用异步并等待(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'));
}

}

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

0

使用BlueBird怎么

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

0

这是另一种方法(带有std Promise的ES6)。使用lodash /下划线类型退出条件(返回=== 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)
    })
};

0

使用标准的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)
})

0

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

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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.