Лучший способ вызова асинхронной функции внутри карты?

Я сопоставляю массив и для одного из возвращаемых значений нового объекта, мне нужно сделать асинхронный вызов.

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
  }
});

Реализация этой функции будет выглядеть примерно так:

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');
    }
  });
}

Я не знаю, какой лучший подход для этого... promises? Вложенные обратные вызовы? Использовать что-то в ES6 или ES7, а затем перетащить с помощью Babel?

Каков наилучший способ реализации этого?

Спасибо!

Ответ 1

Одним из подходов является Promise.all (ES6).

Этот ответ будет работать в Узле 4. 0+. В старых версиях потребуется полифилл или библиотека Promise. Я также использовал функции стрелок ES6, которые можно заменить обычной function для узла <4.

Этот метод вручную оборачивает request.get Обещанием. Вы также можете использовать библиотеку, как запрос-обещание.

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);
  })

Ответ 2

обновление в 2018: Promise.all функцию Promise.all карты проще реализовать:

    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: я поставил общий пример для всех:

Edit @2019/06/19: async/await должен иметь try/catch для обработки ошибки, в противном случае выдается предупреждение;

let data = await Promise.all(data.map(async (item) => {
      try {
      item.fetchItem = await fetchFunc(item.fetchParams);

      return item; 
      } catch(err) {
         throw err;
      }
  });

Ответ 3

Вы можете использовать async.map.

var async = require('async');

async.map(teachers, mapTeacher, function(err, results){
  // results is now an array of stats for each file
});

function mapTeacher(teacher, done) {
  // computing stuff here...
  done(null, teacher);
}

обратите внимание, что все учителя будут обрабатываться параллельно - вы также можете использовать следующие функции:

mapSeries(arr, iterator, [callback]) отображается один за другим

mapLimit(arr, limit, iterator, [callback]) отображает limit в то же время

Ответ 4

2019

предупреждение с Promise.all, они не выполняются в точном порядке, некоторые серверы не могут поддерживать одновременное выполнение запросов или API-вызовов.

Более новые библиотеки делают мои одностраничные приложения более 4 мегапикселями. Поэтому я решил не добавлять больше новых библиотек, таких как lodash и т.д., Которые можно заменить кодом. Я хорошие решения для использования ждут с картой:

 rows.map (  ( record ) => {
    try {
      (async () => {
        let col =  await client.query('SELECT * FROM customers');
      })(); 
    } catch (err) {
      console.log(err);
    }
 });

Ответ 5

Я использую асинхронную функцию над массивом. И не использовать array.map, а для функции. Это что-то вроде этого:

const resultingProcessedArray = async function getSomeArray() {
    try {
      let { data } = await axios({url: '/myUrl', method:'GET'}); //initial array
      let resultingProcessedArray = [];
      for (let i = 0, len = data.items.length; i < len; i++) {
        let results = await axios({url: `/users?filter=id eq ${data.items[i].someId}`, method:'GET'});
        let domainName = results.data.items[0].domainName;
        resultingProcessedArray.push(Object.assign(data.items[i], {domainName}));
      }
      return resultingProcessedArray;
    } catch (err) {
      console.error("Unable to fetch the data", err);
      return [];
    }
};

Ответ 6

Я должен был написать это, для удобства. В противном случае мне может понадобиться https://github.com/mcollina/make-promises-safe

export async function mapAsync<T, U>(
  arr: T[], 
  callbackfn: (value: T, index: number, array: T[]) => Promise<U>, 
  thisArg?: any
) {
  return await Promise.all(arr.map(async (value, index, array) => {
    try {
      return await callbackfn(value, index, array);
    } catch(e) {
      throw e;
    }
  }, thisArg));
}