与承诺循环
用承诺做一个while循环的习惯用法是什么? 所以:
如果情况仍然存在,请做一些事情再重复一遍,然后做一些其他的事情。
dosomething.then(possilblydomoresomethings).then(finish)
我这样做,我想知道是否有更好的/更多的idomatic方法?
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')});
输出:1 2 3 4 5 6 7 8 9 10 11完成
我会使用一个对象来包装的价值。 这样,你可以有一个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'); });
这是一个我认为非常清晰的可重用函数。
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();
这是我发现expression基本模式的最简单方法:定义一个调用promise的函数,检查其结果,然后再调用自己或终止。
const doSomething = value => new Promise(resolve => setTimeout(() => resolve(value >= 5 ? 'ok': 'no'), 1000)) const loop = value => doSomething(value).then(result => { if (result === 'ok') { console.log('yay') } else { console.log(value) return loop(value + 1) } }) loop(1).then(() => console.log('all done!'))
看到它在JSBin的行动
如果您使用承诺解决或拒绝,您将定义then
catch
而不是使用if子句。
如果你有一系列的承诺,你只需要改变loop
来移动或者每次popup下一个。
这是为蓝鸟而不是q,但是因为你没有特别提到q ..在bluebird api文档中,作者提到返回一个承诺生成函数比使用延迟更为习惯。
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); })
由于我不能评论斯图尔特K的答案,我会在这里添加一点点。 基于斯图尔特K的答案,你可以把它归结为一个令人惊讶的简单的概念: 重用未履行的承诺 。 他所拥有的基本上是:
- 创build一个延期承诺的新实例
- 定义你想要在循环中调用的函数
- 在这个function里面:
- 检查你是否完成; 当你解决在#1中创build的承诺并将其返回。
- 如果没有完成,则告诉Q使用现有的promise,并运行“recursion”函数的未完成函数,否则将失败。 Q. when(promise,yourFunction,failFunction)
- 定义你的函数后,使用Q来首次使用Q.nextTick(yourFunction)触发函数,
- 最后把你的新承诺交给调用者(这将触发整个事情的开始)。
斯图尔特的答案是更通用的解决scheme,但基础是可怕的(一旦你意识到它是如何工作的)。
这种模式现在更容易通过使用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'); });
这里是对Promise
原型的扩展,以模仿for
循环的行为。 它支持初始化,条件,循环体和增量部分的承诺或立即值。 它也完全支持exception,并没有内存泄漏。 下面给出如何使用它的例子。
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); });
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())
我现在使用这个:
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); }
这接受一个数组arr
和一个函数,并返回一个Promise
。 提供的函数被调用一次为数组中的每个元素,并获得通过当前元素,它是在数组中的索引。 它可能是同步或asynchronous,在这种情况下,它必须返回一个Promise。
你可以像这样使用它:
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); })
数组中的每个项目将依次处理。 一旦完成所有处理,给予.catch()
的代码将运行,或者,如果发生错误,则给予.catch()
的代码。 在work
函数内部,你可以throw
一个Error
(在同步函数的情况下)或reject
Promise
(在asynchronous函数的情况下)来中止循环。
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); })
使用ES6诺言,我想出了这个。 它locking了承诺,并回报了一个承诺。 这在技术上不是一个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.'); });
我想我不妨把自己的帽子扔在戒指里,用ES6诺言…
function until_success(executor){ var before_retry = undefined; var outer_executor = function(succeed, reject){ var rejection_handler = function(err){ if(before_retry){ try { var pre_retry_result = before_retry(err); if(pre_retry_result) return succeed(pre_retry_result); } catch (pre_retry_error){ return reject(pre_retry_error); } } return new Promise(executor).then(succeed, rejection_handler); } return new Promise(executor).then(succeed, rejection_handler); } var outer_promise = new Promise(outer_executor); outer_promise.before_retry = function(func){ before_retry = func; return outer_promise; } return outer_promise; }
executor
参数与传递给Promise
构造函数的参数相同 ,但会一直调用,直到触发成功callback。 before_retry
函数允许在失败的尝试上自定义error handling。 如果它返回一个真值,它将被认为是一种成功的forms,并且“循环”将结束,作为结果的真相。 如果没有注册before_retry
函数,或者返回falsey值,那么循环将运行另一个迭代。 第三个选项是before_retry
函数本身抛出一个错误。 如果发生这种情况,则“循环”将结束,并将该错误作为错误传递。
这里是一个例子:
var counter = 0; function task(succ, reject){ setTimeout(function(){ if(++counter < 5) reject(counter + " is too small!!"); else succ(counter + " is just right"); }, 500); // simulated async task } until_success(task) .before_retry(function(err){ console.log("failed attempt: " + err); // Option 0: return falsey value and move on to next attempt // return // Option 1: uncomment to get early success.. //if(err === "3 is too small!!") // return "3 is sort of ok"; // Option 2: uncomment to get complete failure.. //if(err === "3 is too small!!") // throw "3rd time, very unlucky"; }).then(function(val){ console.log("finally, success: " + val); }).catch(function(err){ console.log("it didn't end well: " + err); })
选项0的输出:
failed attempt: 1 is too small!! failed attempt: 2 is too small!! failed attempt: 3 is too small!! failed attempt: 4 is too small!! finally, success: 5 is just right
选项1的输出:
failed attempt: 1 is too small!! failed attempt: 2 is too small!! failed attempt: 3 is too small!! finally, success: 3 is sort of ok
选项2的输出:
failed attempt: 1 is too small!! failed attempt: 2 is too small!! failed attempt: 3 is too small!! it didn't end well: 3rd time, very unlucky
我写了一个模块,它可以帮助你做承诺的asynchronous任务链循环,它是基于上面提供的答案juandopazo
/** * Should loop over a task function which returns a "wrapper" object * until wrapper.done is true. A seed value wrapper.seed is propagated to the * next run of the loop. * * todo/maybe? Reject if wrapper is not an object with done and seed keys. * * @param {Promise|*} seed * @param {Function} taskFn * * @returns {Promise.<*>} */ function seedLoop(seed, taskFn) { const seedPromise = Promise.resolve(seed); return seedPromise .then(taskFn) .then((wrapper) => { if (wrapper.done) { return wrapper.seed; } return seedLoop(wrapper.seed, taskFn); }); } // A super simple example of counting to ten, which doesn't even // do anything asynchronous, but if it did, it should resolve to // a promise that returns the { done, seed } wrapper object for the // next call of the countToTen task function. function countToTen(count) { const done = count > 10; const seed = done ? count : count + 1; return {done, seed}; } seedLoop(1, countToTen).then((result) => { console.log(result); // 11, the first value which was over 10. });