Правильный способ написать циклы для обещания.
Как правильно построить цикл, чтобы убедиться, что после обещать звонок и приковали регистратор.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');
});
хотя это, кажется, работает, но я не думаю, что он гарантирует порядок вызова регистратор.log (res);
Есть предложения?
10 ответов:
Я не думаю, что это гарантирует порядок вызова логгера.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()функция для этой и других целей, то во что бы то ни стало сделать это, используя упрощения Берги. Однако из-за того, как работают обещания, передача обратных вызовов таким образом, как правило, не нужна и заставляет вас прыгать через сложные маленькие обручи.насколько я могу судить, вы пытаетесь:
- асинхронно извлекать ряд пользовательских данных для коллекции адресов электронной почты (по крайней мере, это единственный сценарий, который имеет смысл).
- чтобы сделать это, построив
.then()цепь через рекурсию.- для поддержания исходного порядка при обработке возвращенных результатов.
таким образом, проблема на самом деле обсуждается в разделе "Коллекция 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или это связано . Предел (10 в вопросе) определяется полностью по длине массиваarrayOfEmailAddys.
вот как я это делаю со стандартным объектом 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)
дано
- функция asyncFn
- массив элементов
требуются
- обещать сцепления .затем () последовательно (по порядку)
- родной 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())
предложенная функция Берги действительно хороша:
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 (также если действие() никогда не выполняется). См. пример:
var count = 10; util.promiseWhile( function condition() { return count > 0; }, function action() { return new Promise(function(resolve, reject) { count = count - 1; resolve(count) }) }, count)
Я бы сделал что-то вроде этого:
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-это упорядоченный массив всех элементов журнала. И операция журнала будет выполняться, когда все обещания будут выполнены.
есть новый способ решить эту проблему, и это с помощью async / await.
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 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); }); }); }
вот еще один метод (ES6 W / std Promise). Использует критерии выхода типа lodash / underscore (return === 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) }) };
Comments