node.js 带有承诺的 while 循环
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17217736/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
While loop with promises
提问by Grummle
What would be the idiomatic way to do something like a while loop with promises. So:
用 Promise 执行 while 循环之类的惯用方法是什么。所以:
do something if the condition still stands do it again repeat then do something else.
如果条件仍然成立,请做一些事情,再重复一遍,然后再做其他事情。
dosomething.then(possilblydomoresomethings).then(finish)
I've done it this way I was wondering if there were any better/more idomatic ways?
我已经这样做了,我想知道是否有更好/更惯用的方法?
var q = require('q');
var index = 1;
var useless = function(){
var currentIndex = index;
console.log(currentIndex)
var deferred = q.defer();
setTimeout(function(){
if(currentIndex > 10)
deferred.resolve(false);
else deferred.resolve(true);
},500);
return deferred.promise;
}
var control = function(cont){
var deferred = q.defer();
if(cont){
index = index + 1;
useless().then(control).then(function(){
deferred.resolve();
});
}
else deferred.resolve();
return deferred.promise;
}
var chain = useless().then(control).then(function(){console.log('done')});
Output: 1 2 3 4 5 6 7 8 9 10 11 done
输出: 1 2 3 4 5 6 7 8 9 10 11 完成
采纳答案by juandopazo
I'd use an object to wrap the value. That way you can have a doneproperty to let the loop know you're done.
我会使用一个对象来包装值。这样你就可以有一个done属性让循环知道你已经完成了。
// fn should return an object like
// {
// done: false,
// value: foo
// }
function loop(promise, fn) {
return promise.then(fn).then(function (wrapper) {
return !wrapper.done ? loop(Q(wrapper.value), fn) : wrapper.value;
});
}
loop(Q.resolve(1), function (i) {
console.log(i);
return {
done: i > 10,
value: i++
};
}).done(function () {
console.log('done');
});
回答by Stuart K
Here's a reusable function that I think is pretty clear.
这是一个我认为很清楚的可重用函数。
var Q = require("q");
// `condition` is a function that returns a boolean
// `body` is a function that returns a promise
// returns a promise for the completion of the loop
function promiseWhile(condition, body) {
var done = Q.defer();
function loop() {
// When the result of calling `condition` is no longer true, we are
// done.
if (!condition()) return done.resolve();
// Use `when`, in case `body` does not return a promise.
// When it completes loop again otherwise, if it fails, reject the
// done promise
Q.when(body(), loop, done.reject);
}
// Start running the loop in the next tick so that this function is
// completely async. It would be unexpected if `body` was called
// synchronously the first time.
Q.nextTick(loop);
// The promise
return done.promise;
}
// Usage
var index = 1;
promiseWhile(function () { return index <= 11; }, function () {
console.log(index);
index++;
return Q.delay(500); // arbitrary async
}).then(function () {
console.log("done");
}).done();
回答by lawrence
This is the simplest way I've found to express the basic pattern: you define a function that calls the promise, checks its result, and then either calls itself again or terminates.
这是我发现的表达基本模式的最简单方法:您定义一个函数来调用 promise,检查其结果,然后再次调用自身或终止。
const doSomething = value =>
new Promise(resolve =>
setTimeout(() => resolve(value >= 5 ? 'ok': 'no'), 1000))
const loop = value =>
doSomething(value).then(result => {
console.log(value)
if (result === 'ok') {
console.log('yay')
} else {
return loop(value + 1)
}
})
loop(1).then(() => console.log('all done!'))
If you were using a promise that resolves or rejects, you would define thenand catchinstead of using an if-clause.
如果您使用的是解决或拒绝的承诺,您将定义thenandcatch而不是使用 if 子句。
If you had an array of promises, you would just change loopto shift or pop the next one each time.
如果您有一系列 promise,您只需更改loop为每次移动或弹出下一个。
EDIT: Here's a version that uses async/await, because it's 2018:
编辑:这是一个使用 的版本async/await,因为它是 2018 年:
const loop = async value => {
let result = null
while (result != 'ok') {
console.log(value)
result = await doSomething(value)
value = value + 1
}
console.log('yay')
}
As you can see, it uses a normal while loop and no recursion.
如您所见,它使用普通的 while 循环并且没有递归。
回答by aarosil
This is for bluebird not q but since you didn't mention q specifically.. in the bluebird api doc the author mentions returning a promise-generating function would be more idiomatic than using deferreds.
这是针对 bluebird 而不是 q 但因为您没有特别提到 q .. 在 bluebird api doc 中,作者提到返回承诺生成函数将比使用延迟更惯用。
var Promise = require('bluebird');
var i = 0;
var counter = Promise.method(function(){
return i++;
})
function getAll(max, results){
var results = results || [];
return counter().then(function(result){
results.push(result);
return (result < max) ? getAll(max, results) : results
})
}
getAll(10).then(function(data){
console.log(data);
})
回答by millebi
Since I can't comment on Stuart K's answer I'll add a little bit here. Based on Stuart K's answer you can boil it down to a surprisingly simple concept: Reuse an unfulfilled promise. What he has is essentially:
由于我无法对 Stuart K 的回答发表评论,因此我将在这里补充一点。根据 Stuart K 的回答,您可以将其归结为一个非常简单的概念:重用未履行的承诺。他所拥有的基本上是:
- Create a new instance of a deferred promise
- Define your function that you want to call in a loop
- Inside that function:
- Check to see if you're done; and when you are resolve the promise created in #1 and return it.
- If you are not done then tell Q to use the existing promise and run the unfullfilled function that is the "recursive" function, or fail if it died. Q.when(promise, yourFunction, failFunction)
- After defining your function use Q to trigger the function for the first time using Q.nextTick(yourFunction)
- Finally return your new promise to the caller (which will trigger the whole thing to start).
- 创建延迟承诺的新实例
- 定义要在循环中调用的函数
- 在该函数内部:
- 检查你是否已经完成;当你解决 #1 中创建的承诺并返回它时。
- 如果你没有完成,那么告诉 Q 使用现有的承诺并运行未完成的函数,即“递归”函数,或者如果它死了就失败。Q.when(promise, yourFunction, failFunction)
- 定义函数后,使用 Q 首次使用 Q.nextTick(yourFunction) 触发函数
- 最后将你的新承诺返回给调用者(这将触发整个事情开始)。
Stuart's answer is for a more generic solution, but the basics are awesome (once you realize how it works).
斯图尔特的答案是一个更通用的解决方案,但基础知识很棒(一旦你意识到它是如何工作的)。
回答by Joe Hildebrand
This pattern is now more easily called by using q-flow. An example, for the above problem:
现在使用q-flow更容易调用此模式。一个例子,对于上面的问题:
var q = require('q');
require('q-flow');
var index = 1;
q.until(function() {
return q.delay(500).then(function() {
console.log(index++);
return index > 10;
});
}).done(function() {
return console.log('done');
});
回答by user3707531
Here is an extensions to the Promiseprototype to mimic the behavior of a forloop. It supports promises or immediate values for the initialization, condition, loop body, and increment sections. It also has full support for exceptions, and it does not have memory leaks. An example is given below on how to use it.
这是Promise原型的扩展以模仿for循环的行为。它支持初始化、条件、循环体和增量部分的承诺或立即值。它还完全支持异常,并且没有内存泄漏。下面举例说明如何使用它。
var Promise = require('promise');
// Promise.loop([properties: object]): Promise()
//
// Execute a loop based on promises. Object 'properties' is an optional
// argument with the following fields:
//
// initialization: function(): Promise() | any, optional
//
// Function executed as part of the initialization of the loop. If
// it returns a promise, the loop will not begin to execute until
// it is resolved.
//
// Any exception occurring in this function will finish the loop
// with a rejected promise. Similarly, if this function returns a
// promise, and this promise is reject, the loop finishes right
// away with a rejected promise.
//
// condition: function(): Promise(result: bool) | bool, optional
//
// Condition evaluated in the beginning of each iteration of the
// loop. The function should return a boolean value, or a promise
// object that resolves with a boolean data value.
//
// Any exception occurring during the evaluation of the condition
// will finish the loop with a rejected promise. Similarly, it this
// function returns a promise, and this promise is rejected, the
// loop finishes right away with a rejected promise.
//
// If no condition function is provided, an infinite loop is
// executed.
//
// body: function(): Promise() | any, optional
//
// Function acting as the body of the loop. If it returns a
// promise, the loop will not proceed until this promise is
// resolved.
//
// Any exception occurring in this function will finish the loop
// with a rejected promise. Similarly, if this function returns a
// promise, and this promise is reject, the loop finishes right
// away with a rejected promise.
//
// increment: function(): Promise() | any, optional
//
// Function executed at the end of each iteration of the loop. If
// it returns a promise, the condition of the loop will not be
// evaluated again until this promise is resolved.
//
// Any exception occurring in this function will finish the loop
// with a rejected promise. Similarly, if this function returns a
// promise, and this promise is reject, the loop finishes right
// away with a rejected promise.
//
Promise.loop = function(properties)
{
// Default values
properties = properties || {};
properties.initialization = properties.initialization || function() { };
properties.condition = properties.condition || function() { return true; };
properties.body = properties.body || function() { };
properties.increment = properties.increment || function() { };
// Start
return new Promise(function(resolve, reject)
{
var runInitialization = function()
{
Promise.resolve().then(function()
{
return properties.initialization();
})
.then(function()
{
process.nextTick(runCondition);
})
.catch(function(error)
{
reject(error);
});
}
var runCondition = function()
{
Promise.resolve().then(function()
{
return properties.condition();
})
.then(function(result)
{
if (result)
process.nextTick(runBody);
else
resolve();
})
.catch(function(error)
{
reject(error);
});
}
var runBody = function()
{
Promise.resolve().then(function()
{
return properties.body();
})
.then(function()
{
process.nextTick(runIncrement);
})
.catch(function(error)
{
reject(error);
});
}
var runIncrement = function()
{
Promise.resolve().then(function()
{
return properties.increment();
})
.then(function()
{
process.nextTick(runCondition);
})
.catch(function(error)
{
reject(error);
});
}
// Start running initialization
process.nextTick(runInitialization);
});
}
// Promise.delay(time: double): Promise()
//
// Returns a promise that resolves after the given delay in seconds.
//
Promise.delay = function(time)
{
return new Promise(function(resolve)
{
setTimeout(resolve, time * 1000);
});
}
// Example
var i;
Promise.loop({
initialization: function()
{
i = 2;
},
condition: function()
{
return i < 6;
},
body: function()
{
// Print "i"
console.log(i);
// Exception when 5 is reached
if (i == 5)
throw Error('Value of "i" reached 5');
// Wait 1 second
return Promise.delay(1);
},
increment: function()
{
i++;
}
})
.then(function()
{
console.log('LOOP FINISHED');
})
.catch(function(error)
{
console.log('EXPECTED ERROR:', error.message);
});
回答by Lucas Rocha
var Q = require('q')
var vetor = ['a','b','c']
function imprimeValor(elements,initValue,defer){
console.log( elements[initValue++] )
defer.resolve(initValue)
return defer.promise
}
function Qloop(initValue, elements,defer){
Q.when( imprimeValor(elements, initValue, Q.defer()), function(initValue){
if(initValue===elements.length){
defer.resolve()
}else{
defer.resolve( Qloop(initValue,elements, Q.defer()) )
}
}, function(err){
defer.reject(err)
})
return defer.promise
}
Qloop(0, vetor,Q.defer())
回答by Stijn de Witt
I am now using this:
我现在正在使用这个:
function each(arr, work) {
function loop(arr, i) {
return new Promise(function(resolve, reject) {
if (i >= arr.length) {resolve();}
else try {
Promise.resolve(work(arr[i], i)).then(function() {
resolve(loop(arr, i+1))
}).catch(reject);
} catch(e) {reject(e);}
});
}
return loop(arr, 0);
}
This accepts an array arrand a function workand returns a Promise. The supplied function gets called once for each element in the array and gets passed the current element and it's index in the array. It may be sync or async, in which case it must return a Promise.
这接受一个数组arr和一个函数work并返回一个Promise. 提供的函数为数组中的每个元素调用一次,并传递当前元素及其在数组中的索引。它可能是同步的或异步的,在这种情况下它必须返回一个 Promise。
You can use it like this:
你可以这样使用它:
var items = ['Hello', 'cool', 'world'];
each(items, function(item, idx) {
// this could simply be sync, but can also be async
// in which case it must return a Promise
return new Promise(function(resolve){
// use setTimeout to make this async
setTimeout(function(){
console.info(item, idx);
resolve();
}, 1000);
});
})
.then(function(){
console.info('DONE');
})
.catch(function(error){
console.error('Failed', error);
})
Each item in the array will be handled in turn. Once all are handled, the code given to .then()will run, or, if some error occurred, the code given to .catch(). Inside the workfunction, you can throwan Error(in case of synchronous functions) or rejectthe Promise(in case of async functions) to abort the loop.
数组中的每一项都会被依次处理。处理完所有内容后,.then()将运行提供给 的代码,或者,如果发生某些错误,则提供给 的代码.catch()。里面work的功能,你可以throw在Error(在同步功能的情况下)或reject将Promise(在异步功能的情况下)中止循环。
function each(arr, work) {
function loop(arr, i) {
return new Promise(function(resolve, reject) {
if (i >= arr.length) {resolve();}
else try {
Promise.resolve(work(arr[i], i)).then(function() {
resolve(loop(arr, i+1))
}).catch(reject);
} catch(e) {reject(e);}
});
}
return loop(arr, 0);
}
var items = ['Hello', 'cool', 'world'];
each(items, function(item, idx) {
// this could simply be sync, but can also be async
// in which case it must return a Promise
return new Promise(function(resolve){
// use setTimeout to make this async
setTimeout(function(){
console.info(item, idx);
resolve();
}, 1000);
});
})
.then(function(){
console.info('DONE');
})
.catch(function(error){
console.error('Failed', error);
})
回答by mqsoh
Using the ES6 Promise, I came up with this. It chains the promises and returns a promise. It's not technically a while loop, but does show how to iterate over promises synchronously.
使用 ES6 Promise,我想出了这个。它将承诺链接起来并返回一个承诺。从技术上讲,它不是 while 循环,但确实展示了如何同步迭代 promise。
function chain_promises(list, fun) {
return list.reduce(
function (promise, element) {
return promise.then(function () {
// I only needed to kick off some side-effects. If you need to get
// a list back, you would append to it here. Or maybe use
// Array.map instead of Array.reduce.
fun(element);
});
},
// An initial promise just starts things off.
Promise.resolve(true)
);
}
// To test it...
function test_function (element) {
return new Promise(function (pass, _fail) {
console.log('Processing ' + element);
pass(true);
});
}
chain_promises([1, 2, 3, 4, 5], test_function).then(function () {
console.log('Done.');
});

