Correct way to write loops for promise.
如何正确构造一个循环以确保后续的promise调用和链式logger.log(res)通过迭代同步运行?(蓝鸟)
1 | db.getUser(email).then(function(res) { logger.log(res); }); // this is a promise |
我尝试了以下方法(来自http://blog.victorquinn.com/javascript-promise-while-loop的方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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)的顺序;
有什么建议?
如果你真的想要一个通用的
据我所知,你正在尝试:
- 异步获取一组电子邮件地址的一系列用户详细信息(至少,这是唯一有意义的方案)。
-
通过递归构建
.then() 链来实现这一目的。 - 在处理返回的结果时保持原始顺序。
因此,问题实际上是Promise Anti-patterns中"The Collection Kerfuffle"中讨论的问题,它提供了两个简单的解决方案:
-
使用
Array.prototype.map() 进行并行异步调用 -
使用
Array.prototype.reduce() 进行串行异步调用。
并行方法将(直接)给出您试图避免的问题 - 响应的顺序是不确定的。串行方法将构建所需的
1 2 3 4 5 6 7 8 9 | 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()); } |
请致电如下:
1 2 3 4 5 6 | //Compose here, by whatever means, an array of email addresses. var arrayOfEmailAddys = [...]; fetchUserDetails(arrayOfEmailAddys).then(function() { console.log('all done'); }); |
如您所见,不需要丑陋的外部var
I don't think it guarantees the order of calling logger.log(res);
实际上,确实如此。该语句在
Any suggestions?
许多。最重要的是你使用create-promise-manual反模式 - 只做
1 2 3 4 5 6 7 | promiseWhile(…, function() { return db.getUser(email) .then(function(res) { logger.log(res); count++; }); })… |
其次,
1 2 3 4 | var promiseWhile = Promise.method(function(condition, action) { if (!condition()) return; return action().then(promiseWhile.bind(null, condition, action)); }); |
第三,我不会使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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')); |
以下是我使用标准Promise对象的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // 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) |
特定
- asyncFn函数
- 一系列的项目
需要
- 承诺链接.then()串联(按顺序)
- 原生es6
解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 / await。
1 2 3 4 5 6 7 8 9 10 | 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
我会做这样的事情:
1 2 3 4 5 6 7 8 9 10 11 12 | 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是要记录的所有元素的有序数组。所有承诺完成后,日志操作将执行。
Bergi建议的功能非常好:
1 2 3 4 | var promiseWhile = Promise.method(function(condition, action) { if (!condition()) return; return action().then(promiseWhile.bind(null, condition, action)); }); |
在使用promises时,我还想做一个小小的补充,这是有意义的:
1 2 3 4 | var promiseWhile = Promise.method(function(condition, action, lastValue) { if (!condition()) return lastValue; return action().then(promiseWhile.bind(null, condition, action)); }); |
这样,while循环可以嵌入到promise链中,并使用lastValue解析(如果action()永远不会运行)。见例子:
1 2 3 4 5 6 7 8 9 10 11 12 | var count = 10; util.promiseWhile( function condition() { return count > 0; }, function action() { return new Promise(function(resolve, reject) { count = count - 1; resolve(count) }) }, count) |
使用async和await(es6):
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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')); } } |
首先使用promises数组(promise数组)并在使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 } |
使用标准的promise对象,并使promise返回结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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) }) |
这是另一种方法(ES6 w / std Promise)。使用lodash /下划线类型退出条件(return === false)。请注意,您可以在选项中轻松添加exitIf()方法以在doOne()中运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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) }) }; |
使用BlueBird的这个怎么样?
1 2 3 4 5 6 7 | function fetchUserDetails(arr) { return Promise.each(arr, function(email) { return db.getUser(email).done(function(res) { logger.log(res); }); }); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | 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); }); |