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){
   ....
}

Relaterade artiklar