Правильный способ написания циклов для обещания.
Как правильно построить цикл, чтобы убедиться, что следующий обещающий вызов и цепочка logger.log(res) выполняется синхронно через итерацию? (Bluebird)
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');
});
Хотя это работает, но я не думаю, что он гарантирует порядок вызова logger.log(res);
Любые предложения?
Ответы
Ответ 1
Я не думаю, что он гарантирует порядок вызова logger.log(res);
Собственно, так оно и есть. Этот оператор выполняется перед вызовом resolve
.
Любые предложения?
Много. Самое главное - использовать create-prom-hand antipattern - просто делать только
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'));
Ответ 2
Если вам действительно нужна общая функция promiseWhen()
для этой и других целей, тогда обязательно используйте упрощения Bergi. Однако из-за способа работы promises передача обратных вызовов таким образом обычно не нужна и заставляет вас прыгать через сложные маленькие обручи.
Насколько я могу судить, вы пытаетесь:
- для асинхронного выбора серии пользовательских сведений для коллекции адресов электронной почты (по крайней мере, это единственный сценарий, который имеет смысл).
- сделать это, построив цепочку
.then()
через рекурсию.
- чтобы поддерживать исходный порядок обработки возвращаемых результатов.
Определено, таким образом, проблема на самом деле та, что обсуждалась в разделе "Коллекция Kerfuffle" в Promise Anti-patterns, которая предлагает два простых решения:
- параллельные асинхронные вызовы с использованием
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
или связанной с ней функции condition
. Предел (10 в вопросе) полностью определяется длиной массива arrayOfEmailAddys
.
Ответ 3
Вот как я делаю это со стандартным объектом 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)
Ответ 4
Учитывая
- Функция asyncFn
- массив элементов
Обязательный
- обещание цепочки .then() последовательно (по порядку)
- 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())
Ответ 5
Предлагаемая функция Берги действительно хороша:
var promiseWhile = Promise.method(function(condition, action) {
if (!condition()) return;
return action().then(promiseWhile.bind(null, condition, action));
});
Тем не менее, я хочу сделать небольшое дополнение, что имеет смысл при использовании promises:
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)
Ответ 6
Я бы сделал что-то вроде этого:
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 является упорядоченным массивом всех элементов для журнала. И работа журнала будет выполняться, когда будут выполнены все promises.
Ответ 7
Существует новый способ решить эту проблему, используя async/wait.
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
Ответ 8
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);
});
Ответ 9
Как насчет этого с помощью BlueBird?
function fetchUserDetails(arr) {
return Promise.each(arr, function(email) {
return db.getUser(email).done(function(res) {
logger.log(res);
});
});
}
Ответ 10
Здесь другой метод (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)
})
};
Ответ 11
Используя стандартный объект обещания и имея обещание, верните результаты.
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)
})
Ответ 12
Сначала возьмите массив обещаний (массив обещаний), а затем разрешите этот массив обещаний, используя 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
}
Ответ 13
Используйте async и ждите (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'));
}
}