Correct way to write loops for promise.

How to correctly construct a loop to make sure the following promise call and the chained logger.log(res) runs synchronously through iteration? (bluebird)

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

I tried the following way (method from 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');
});

Although it seems to work, but I don't think it guarantees the order of calling logger.log(res);

Any suggestions?

141313 次浏览

我不认为它能保证调用 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'));

如果你真的想要一个通用的 promiseWhen()函数用于这个和其他目的,那么请务必这样做,使用 Bergi 的简化。然而,由于承诺的工作方式,以这种方式传递回调通常是不必要的,并迫使您跳过复杂的小环。

As far as I can tell you're trying :

  • 异步获取一组电子邮件地址的一系列用户详细信息(至少,这是唯一有意义的场景)。
  • 通过递归构建 .then()链来实现。
  • 在处理返回的结果时维护原始顺序。

这样定义的问题实际上是在 承诺反模式的“收集乱码”中讨论的问题,它提供了两个简单的解决方案:

  • parallel asynchronous calls using Array.prototype.map()
  • 使用 Array.prototype.reduce()的串行异步调用。

The parallel approach will (straightforwardly) give the issue that you are trying to avoid - that the order of the responses is uncertain. The serial approach will build the required .then() chain - flat - no recursion.

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

正如您所看到的,没有必要使用丑陋的外部变量 count或它的相关 condition函数。限制(问题中的10)完全由数组 arrayOfEmailAddys的长度决定。

Bergi's suggested function is really nice:

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

不过,在使用承诺时,我还想做一个小小的补充,这很有意义:

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

这样,while 循环可以嵌入到承诺链中,并用 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)

给予

  • 异步 Fn 函数
  • 项目数组

需要

  • promise chaining .then()'s in series (in order)
  • native 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())

Here's how I do it with the standard Promise object.

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

这个用 蓝鸟怎么样?

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

I'd make something like this:

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 是要记录的所有元素的有序数组。日志操作将在所有承诺完成后执行。

下面是另一种方法(ES6w/std 承诺)。使用 loash/下划线类型退出条件(return = = false)。注意,您可以轻松地在 doOne ()中运行的选项中添加 exitIf ()方法。

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

有一种新的方法可以解决这个问题,那就是使用异步/等待。

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

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

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

首先取承诺数组(承诺数组) ,然后用 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
}

Use async and 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'));
}


}