Javascript 在地图中调用异步函数的最佳方法?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33438158/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 15:03:07  来源:igfitidea点击:

Best way to call an async function within map?

javascriptnode.js

提问by magician11

I'm mapping over an array and for one of the return values of the new object, I need to make an async call.

我正在映射一个数组,对于新对象的返回值之一,我需要进行异步调用。

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

The implementation of that function will look something like

该函数的实现看起来像

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

I'm not clear what's the best approach to do this... promises? Nested callbacks? Use something in ES6 or ES7 and then transpile with Babel?

我不清楚这样做的最佳方法是什么......承诺?嵌套回调?在 ES6 或 ES7 中使用某些东西,然后使用 Babel 进行编译?

What's the current best way to implement this?

目前实现这一目标的最佳方法是什么?

Thanks!

谢谢!

回答by joews

One approach is Promise.all(ES6).

一种方法是Promise.all(ES6)

This answer will work in Node 4.0+. Older versions will need a Promise polyfill or library. I have also used ES6 arrow functions, which you could replace with regular functions for Node < 4.

这个答案适用于 Node 4.0+。旧版本需要 Promise polyfill 或库。我还使用了 ES6 箭头函数,您可以将其替换function为 Node < 4 的常规s。

This technique manually wraps request.getwith a Promise. You could also use a library like request-promise.

这种技术request.get用 Promise手动包装。您还可以使用像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);
  })

回答by Kai

update in 2018: Promise.allasync function within map callback is easier to implement:

2018 年更新: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: I put the general example for everyone:

Edit@2018/04/29:我给大家举个一般的例子:

Edit@2019/06/19: async/await should have try/catch to handle error, if not it would throw an warning message;

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

回答by Ezequias Dinella

You can use async.map.

您可以使用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);
}

note that all teachers will be processed in parallel - you can use also this functions:

请注意,所有教师都将并行处理 - 您也可以使用以下功能:

mapSeries(arr, iterator, [callback])maps one by one

mapSeries(arr, iterator, [callback])一张一张的地图

mapLimit(arr, limit, iterator, [callback])maps limitat same time

mapLimit(arr, limit, iterator, [callback])地图limit在同一时间

回答by Tudor Morar

I am using an async function over the array. And not using array.map, but a for function. It's something like this:

我在数组上使用异步函数。而不是使用 array.map,而是一个 for 函数。它是这样的:

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 [];
    }
};

回答by Polv

I had to write this, for the sake of convenience. Otherwise, I might need https://github.com/mcollina/make-promises-safe

为了方便,我不得不写这个。否则,我可能需要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));
}

回答by Nicolas Keller

For production purposes you probably want to use a lib like lodasync, you should not reinvent the wheel:

出于生产目的,您可能想要使用像lodasync这样的,您不应该重新发明轮子:

import { mapAsync } from 'lodasync'

const result = await mapAsync(async(element) => {
  return 3 + await doSomething(element)
}, array)

It uses promises, has no dependencies, and is as fast as it gets.

它使用承诺,没有依赖关系,并且尽可能快。

回答by Thomas Frank

By using Promise.allyou can make mapand forEachwork with async functions (i.e. Promises).

通过使用Promise.all,您可以使mapforEach与异步函数(即 Promises)一起工作。

To make filter, someand everywork you can first use an async map (that in turn uses Promise.all) and then go through the true/false values and synchronously do the filtering/evaluation.

为了使过滤器一些每一个工作,你可以先使用异步映射(即又使用Promise.all),然后再通过真/假值,并同步做好过滤/评估。

To make reduceand reduceRightwork with async functions you can wrap the original function in a new one that waits for the accumulator to resolve.

要使reducereduceRight与异步函数一起工作,您可以将原始函数包装在一个等待累加器解析的新函数中。

Using this knowledge it is possible to modify the original array methods in a way so that they continue to work "as usual" with normal/synchronous functions but will also work with async functions.

使用这些知识,可以以某种方式修改原始数组方法,以便它们继续“照常”使用普通/同步函数,但也可以使用异步函数。

// a 'mini library' (save it somewhere and import it once/project)
(() => {
  let AsyncFunction = Object.getPrototypeOf(async e => e).constructor;
  ['map', 'forEach'].forEach(method => {
    let orgMethod = Array.prototype[method];
    Array.prototype[method] = function (func) {
      let a = orgMethod.call(this, func);
      return func instanceof AsyncFunction ? Promise.all(a) : a;
    };
  });
  ['filter', 'some', 'every'].forEach(method => {
    let orgMethod = Array.prototype[method];
    Array.prototype[method] = function (func) {
      if (func instanceof AsyncFunction) {
        return (async () => {
          let trueOrFalse = await this.map(func);
          return orgMethod.call(this, (_x, i) => trueOrFalse[i]);
        })();
      }
      else {
        return orgMethod.call(this, func);
      }
    };
  });
  ['reduce', 'reduceRight'].forEach(method => {
    let orgMethod = Array.prototype[method];
    Array.prototype[method] = function (...args) {
      if (args[0] instanceof AsyncFunction) {
        let orgFunc = args[0];
        args[0] = async (...args) => {
          args[0] = await args[0];
          return orgFunc.apply(this, args);
        };
      }
      return orgMethod.apply(this, args);
    };
  });
})();

// AND NOW:

// this will work
let a = [1, 2, 3].map(x => x * 3); // => [3, 6, 9]
let b = [1, 2, 3, 4, 5, 6, 7].filter(x => x > 3); // [4, 5, 6, 7]
let c = [1, 2, 3, 4, 5].reduce((acc, val) => acc + val); // => 15

// this will also work
let x = await [1, 2, 3].map(async x => x * 3);
let y = await [1, 2, 3, 4, 5, 6, 7].filter(async x => x > 3);
let z = await [1, 2, 3, 4, 5].reduce(async (acc, val) => acc + val);

回答by richytong

The best way to call an async function within mapis to use a mapcreated expressly for async functions.

在内部调用异步函数的最佳方法map是使用map专门为异步函数创建的。

For a function to be async, it must return a Promise.

对于异步函数,它必须返回一个 Promise。

function urlToBase64(url) {
  return new Promise((resolve, reject) => {
    request.get(url, function (error, response, body) {
      if (error) {
        reject(error)
      } else if (response && response.statusCode == 200) {
        resolve(
          "data:" + response.headers["content-type"] + ";base64," + new Buffer(body).toString('base64');
        )
      } else {
        reject(new Error('invalid response'))
      }
    });
  })
}

Now, we can map:

现在,我们可以映射:

const { pipe, map, get } = require('rubico')

const metafieldTeacherData = {} // { [teacher_id]: {...}, ... }

const parseTeacher = teacher => ({
  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
})

const main = async () => {
  const teachers = [] // array full of teachers
  const firebaseData = await map(pipe([
    parseTeacher,
    get('studioURL'),
    urlToBase64,
  ]))(teachers)
  console.log(firebaseData) // > ['data:application/json;base64,...', ...]
}

main()

rubico's map worries about Promise.allso you don't have to.

rubico 的地图担心,Promise.all所以你不必。