сценарий
У меня есть массив URL-адресов, которые мне нужно загрузить, однако каждый из них также должен быть снабжен уникальным идентификатором транзакции, который должен быть запрошен с сервера и только увеличивается при успешном выполнении запроса.
проблема
Когда я прохожу через массив, мне нужно дождаться как запроса идентификатора транзакции, так и запроса на выполнение файла до начала следующей итерации цикла, но количество файлов не фиксировано, поэтому необходимо динамически построить цепочку обещания.
ПСЕВДОКОД
Ниже приведен псевдокод, getFiles() - проблема, потому что все запросы получают один и тот же идентификатор транзакции, поскольку они не дождались завершения предыдущего запроса.
function getTransationId(){
return new Promise((resolve,reject)=> {
let id = getNextTransactionId();
if(id!=error){
resolve(id);
}else{
reject(error);
}
})
}
function getFile(url, transactionId){
return new Promise((resolve,reject)=>{
http.request(url+transactionId, function(err,response){
if(err){
reject(err);
}else{
resolve(response);
}
});
});
}
function getFilesFromArray(urlArray){
for(let url of urlArray){
getTransactionId().then(resolve=>getFile(url,resolve),reject=>console.error(reject));
}
}
Вопрос
Как объединить цепочные обещания вместе динамически?
Ответ
Функциональный подход состоит в том, чтобы использовать reduce
для повторения и возвращать окончательное обещание, прикованное по каждому подзадачу. Это также помогает создавать результаты, например, в массиве:
function getFilesFromArray(urlArray){
const filesPromise = urlArray.reduce((curPromise, url) => {
return curPromise
.then(curFiles => {
return getTransactionId()
.then(id => getFile(url, id))
.then(newFile => [...curFiles, newFile]);
});
}, Promise.resolve([]));
filesPromise.then(files => {
console.log(files);
}
}
Это эффективно создает цепочку обещаний, которая:
Promise
со значением []
представляющим начальный набор файлов: Promise.resolve([])
curPromise
в цепочке, а затемgetTransactionId
и использует id для getFile
curFiles
установленный в curPromise
(предыдущие значения) и объединит в него новый файлПопробуйте использовать async/wait.
Подробнее здесь
async function getFilesFromArray(urlArray) {
for(let url of urlArray){
//wrap this in a try/catch block if you want to continue with execution
//if you receive an error from one of the functions
const transactionId =await getTransactionId()
const file = await getFile(url,transactionId)
}
}
Вы можете сделать что-то в этом направлении
function getAllFiles(i, results, urlArray) {
if(i == urlArray.length) return;
getTransationId().then(id => {
return new Promise((resolve, reject) => {
http.request(urlArray[i] + id, (err, response) => {
if(err){
reject();
}else{
results.push(response);
resolve();
}
});
});
}).then(() => {
getAllFiles(i + 1, results, urlArray);
})
}
Вы можете упростить логику, если вы запускаете ее через синхронный исполнитель nsynjs. Nsynjs приостанавливается, когда какая-то функция оценивает обещание, а затем присваивает результат свойству data
. Код будет выглядеть следующим образом:
function getFilesFromArray(urlArray){
for(var i = 0; i<urlArray.length; i++) {
var trId = getTransactionId().data;
// trId is ready here
var fileContent = getFile(urlArray[i],trId).data;
// file data is ready here
console.log('fileContent=',fileContent);
};
};
nsynjs.run(getFilesFromArray,{},urls,function(){
console.log('getFilesFromArray is done');
});
getFilesFromArray можно дополнительно упростить до:
function getFilesFromArray(urlArray){
for(var i = 0; i<urlArray.length; i++) {
var fileContent = getFile(urlArray[i],getTransactionId().data).data;
console.log('fileContent=',fileContent);
};
};
[...a, b]
это новый синтаксис для меня, и это прекрасно. Благодарю.