JavaScript数组.reduce与async / await

时间:2016-12-20 13:26:29

标签: javascript promise async-await reduce ecmascript-next

似乎有一些问题将async / await与.reduce()结合起来,如下所示:

const data = await bodies.reduce(async(accum, current, index) => {
  const methodName = methods[index]
  const method = this[methodName]
  if (methodName == 'foo') {
    current.cover = await this.store(current.cover, id)
    console.log(current)
    return {
      ...accum,
      ...current
    }
  }
  return {
    ...accum,
    ...method(current.data)
  }
}, {})
console.log(data)

data完成之前记录了this.store对象...

我知道您可以将Promise.all与异步循环一起使用,但这是否适用于.reduce()

8 个答案:

答案 0 :(得分:66)

问题是你的累加器值是promises - 它们是async function s的返回值。要获得顺序评估(除了最后一次迭代之外的所有迭代),您需要使用

const data = await array.reduce(async (accumP, current, index) => {
  const accum = await accumP;
  …
}, Promise.resolve(…));

也就是说,对于async / await,我一般会向use plain loops instead of array iteration methods推荐,它们的性能更高,而且往往更简单。

答案 1 :(得分:2)

我喜欢Bergi的回答,我认为这是正确的方法。

我还想提一下我的名为Awaity.js

的图书馆

让您毫不费力地使用reducemap等功能。 filterasync / await

import reduce from 'awaity/reduce';

const posts = await reduce([1,2,3], async (posts, id) => {

  const res = await fetch('/api/posts/' + id);
  const post = await res.json();

  return {
    ...posts,
    [id]: post
  };
}, {})

posts // { 1: { ... }, 2: { ... }, 3: { ... } }

答案 2 :(得分:2)

有时最好的办法就是将两个代码版本并排放置,同步和异步:

同步版本:

const arr = [1, 2, 3, 4, 5];

const syncRev = arr.reduce((acc, i) => [i, ...acc], []); // [5, 4, 3, 2, 1] 

异步一:

(async () => { 
   const asyncRev = await arr.reduce(async (promisedAcc, i) => {
      const id = await asyncIdentity(i); // could be id = i, just stubbing async op.
      const acc = await promisedAcc;
      return [id, ...acc];
   }, Promise.resolve([]));   // [5, 4, 3, 2, 1] 
})();

//async stuff
async function asyncIdentity(id) {
   return Promise.resolve(id);
}

const arr = [1, 2, 3, 4, 5];
(async () => {
    const asyncRev = await arr.reduce(async (promisedAcc, i) => {
        const id = await asyncIdentity(i);
        const acc = await promisedAcc;
        return [id, ...acc];
    }, Promise.resolve([]));

    console.log('asyncRev :>> ', asyncRev);
})();

const syncRev = arr.reduce((acc, i) => [i, ...acc], []);

console.log('syncRev :>> ', syncRev);

async function asyncIdentity(id) {
    return Promise.resolve(id);
}

答案 3 :(得分:1)

您可以将整个map / reduce迭代器块包装到自己的Promise.resolve中并等待它完成。但问题是,累加器不包含您在每次迭代时所期望的结果数据/对象。由于内部异步/等待/承诺链,累加器本身将是实际的Promises,尽管在您调用商店之前使用了await关键字,但它们可能尚未自行解决(这可能会让您相信迭代赢得了#39; t实际上返回,直到该调用完成并且累加器被更新。

虽然这不是最优雅的解决方案,但您可以选择将数据对象变量移出范围并将其指定为 let ,以便正确绑定和突变可以发生。然后在async / await / Promise调用解析时从迭代器内部更新此数据对象。

/* allow the result object to be initialized outside of scope 
   rather than trying to spread results into your accumulator on iterations, 
   else your results will not be maintained as expected within the 
   internal async/await/Promise chain.
*/    
let data = {}; 

await Promise.resolve(bodies.reduce(async(accum, current, index) => {
  const methodName = methods[index]
  const method = this[methodName];
  if (methodName == 'foo') {
    // note: this extra Promise.resolve may not be entirely necessary
    const cover = await Promise.resolve(this.store(current.cover, id));
    current.cover = cover;
    console.log(current);
    data = {
      ...data,
      ...current,
    };
    return data;
  }
  data = {
    ...data,
    ...method(current.data)
  };
  return data;
}, {});
console.log(data);

答案 4 :(得分:1)

这是使异步减少的方法:

async function asyncReduce(arr, fn, initialValue) {
  let temp = initialValue;

  for (let idx = 0; idx < arr.length; idx += 1) {
    const cur = arr[idx];

    temp = await fn(temp, cur, idx);
  }

  return temp;
}

答案 5 :(得分:0)

export const addMultiTextData = async(data) => {
  const textData = await data.reduce(async(a, {
    currentObject,
    selectedValue
  }) => {
    const {
      error,
      errorMessage
    } = await validate(selectedValue, currentObject);
    return {
      ...await a,
      [currentObject.id]: {
        text: selectedValue,
        error,
        errorMessage
      }
    };
  }, {});
};

答案 6 :(得分:0)

[未解决OP的确切概率;专注于落在这里的其他人。]

减少通常用于需要前一步骤的结果才能处理下一步骤的情况。在这种情况下,您可以将promise串在一起la:

promise = elts.reduce(
    async (promise, elt) => {
        return promise.then(async last => {
            return await f(last, elt)
        })
    }, Promise.resolve(0)) // or "" or [] or ...

这是一个使用fs.promise.mkdir()的示例(可以肯定,使用mkdirSync要简单得多,但在我的情况下,它是跨网络的):

const Path = require('path')
const Fs = require('fs')

async function mkdirs (path) {
    return path.split(/\//).filter(d => !!d).reduce(
        async (promise, dir) => {
            return promise.then(async parent => {
                const ret = Path.join(parent, dir);
                try {
                    await Fs.promises.lstat(ret)
                } catch (e) {
                    console.log(`mkdir(${ret})`)
                    await Fs.promises.mkdir(ret)
                }
                return ret
            })
        }, Promise.resolve(""))
}

mkdirs('dir1/dir2/dir3')

下面是另一个示例,该示例加上100 + 200 ... 500,并稍等片刻:

async function slowCounter () {
    const ret = await ([100, 200, 300, 400, 500]).reduce(
        async (promise, wait, idx) => {
            return promise.then(async last => {
                const ret = last + wait
                console.log(`${idx}: waiting ${wait}ms to return ${ret}`)
                await new Promise((res, rej) => setTimeout(res, wait))
                return ret
            })
        }, Promise.resolve(0))
    console.log(ret)
}

slowCounter ()

答案 7 :(得分:0)

Bluebird 的另一个经典选项

const promise = require('bluebird');

promise.reduce([1,2,3], (agg, x) => Promise.resolve(agg+x),0).then(console.log);

// Expected to product sum 6