javascript - Bästa sättet att kalla en asynkron funktion inom map?
Jag mappar över en array och för ett av returvärdena i det nya objektet behöver jag göra ett asynkront anrop.
var firebaseData = teachers.map(function(teacher) {
return {
name: teacher.title,
description: teacher.body_html,
image: urlToBase64(teacher.summary_html.match(/src="(.*?)"/)[1]),
city: metafieldTeacherData[teacher.id].city,
country: metafieldTeacherData[teacher.id].country,
state: metafieldTeacherData[teacher.id].state,
studioName: metafieldTeacherData[teacher.id].studioName,
studioURL: metafieldTeacherData[teacher.id].studioURL
}
});
Implementeringen av den funktionen kommer att se ut ungefär som
function urlToBase64(url) {
request.get(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
return "data:" + response.headers["content-type"] + ";base64," + new Buffer(body).toString('base64');
}
});
}
Jag är inte säker på vad som är det bästa sättet att göra detta... löften? Nästlade återanrop? Använd något i ES6 eller ES7 och sedan transpile med Babel?
Vad är det nuvarande bästa sättet att implementera detta?
Svar #1
uppdatering 2018: Promise.all asynkron funktion inom map-callback är lättare att implementera:
let firebaseData = await Promise.all(teachers.map(async teacher => {
return {
name: teacher.title,
description: teacher.body_html,
image: await urlToBase64(teacher.summary_html.match(/src="(.*?)"/)[1]),
city: metafieldTeacherData[teacher.id].city,
country: metafieldTeacherData[teacher.id].country,
state: metafieldTeacherData[teacher.id].state,
studioName: metafieldTeacherData[teacher.id].studioName,
studioURL: metafieldTeacherData[teacher.id].studioURL
}
}));
async function urlToBase64(url) {
return request.get(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
return "data:" + response.headers["content-type"] + ";base64," + new Buffer(body).toString('base64');
}
});
}
Edit@2018/04/29: Jag satte det allmänna exemplet för alla:
Edit@2019/06/19 : async/await bör ha try/catch för att hantera fel för att få din process att fortsätta fungera om några av förfrågningarna misslyckas.
let data = await Promise.all(data.map(async (item) => {
try {
item.fetchItem = await fetchFunc(item.fetchParams);
return item;
} catch(error) {
return {...item, error } ;
}
}));
/* we can filter errors in data and retry later
* eg:
* const errorItems = data.filter(item => !!item.error)
*/
Svar #2
Ett tillvägagångssätt är Promise.all (ES6).
Detta svar kommer att fungera i Node 4.0+. Äldre versioner kommer att behöva en Promise-polyfill eller ett bibliotek. Jag har också använt ES6-pilfunktioner, som du kan ersätta med vanliga function-funktioner för Node < 4.
Denna teknik omsluter manuellt request.get med ett Promise. Du kan också använda ett bibliotek som request-promise.
function urlToBase64(url) {
return new Promise((resolve, reject) => {
request.get(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve("data:" + response.headers["content-type"] + ";base64," + new Buffer(body).toString('base64'));
} else {
reject(response);
}
});
})
}
// Map input data to an Array of Promises
let promises = input.map(element => {
return urlToBase64(element.image)
.then(base64 => {
element.base64Data = base64;
return element;
})
});
// Wait for all Promises to complete
Promise.all(promises)
.then(results => {
// Handle results
})
.catch(e => {
console.error(e);
})
Svar #3
År 2020 har vi nu for await...of-syntaxen från ECMAScript2021 som förenklar saker avsevärt:
Så du kan nu helt enkelt göra detta:
//return an array of promises from our iteration:
let promises = teachers.map(async m => {
return await request.get(....);
});
//simply iterate those
//val will be the result of the promise not the promise itself
for await (let val of promises){
....
}
