1

I am using promise function like this:

// WORK
let res = {approveList: [], rejectList: [], errorId: rv.errorId, errorDesc: rv.errorDesc};
for (let i = 0; i < rv.copyDetailList.length; i ++) {
    const item = rv.copyDetailList[i];
    const v = await convertCommonInfo(item);
    if (!item.errorId) {
        res.approveList.push(v);
    } else {
        res.rejectList.push(merge(v, {errorId: item.errorId, errorDesc: item.errorMsg}));
    }
}

This works well, but I want to try to use some functional function, I find that I have to use map then reduce

// WORK, But with two traversal
const dataList = await promise.all(rv.copyDetailList.map((item) => convertCommonInfo(item)));

const res = dataList.reduce((obj, v, i) => {
    const item = rv.copyDetailList[i];
    if (!item.errorId) {
        obj.approveList.push(v);
    } else {
        obj.rejectList.push(merge(v, {errorId: item.errorId, errorDesc: item.errorMsg}));
    }

    return obj;
}, {approveList: [], rejectList: [], errorId: rv.errorId, errorDesc: rv.errorDesc});

I find that forEach function can not work:

// NOT WORK, not wait async function
rv.copyDetailList.forEach(async function(item) {
    const v = await convertCommonInfo(item);
    if (!item.errorId) {
        res.approveList.push(v);
    } else {
        res.rejectList.push(merge(v, {errorId: item.errorId, errorDesc: item.errorMsg}));
    }
});

This doesn't work, it just return init value. In fact this puzzle me, sine I await the function, why not work?

Even I want to use reduce function:

// NOT WORK, Typescirpt can not compile
rv.copyDetailList.reduce(async function(prev, item) {
    const v = await convertCommonInfo(item);
    if (!item.errorId) {
        prev.approveList.push(v);
    } else {
        prev.rejectList.push(merge(v, {errorId: item.errorId, errorDesc: item.errorMsg}));
    }
}, res);

But since I am using Typescript, I got error like this:

error TS2345: Argument of type '(prev: { approveList: any[]; rejectList: any[]; errorId: string; errorDesc: string; }, item: Resp...' is not assignable to parameter of type '(previousValue: { approveList: any[]; rejectList: any[]; errorId: string; errorDesc: string; }, c...'.
Type 'Promise<void>' is not assignable to type '{ approveList: any[]; rejectList: any[]; errorId: string; errorDesc: string; }'.
Property 'approveList' is missing in type 'Promise<void>'.

So I want to know two things:

  1. Why forEach await can not work?
  2. Can I use promise function in reduce?
roger
  • 9,063
  • 20
  • 72
  • 119
  • possible duplicate of [Using async/await with a forEach loop](http://stackoverflow.com/q/37576685/1048572) (it's the same for `reduce`) – Bergi Aug 17 '16 at 00:21

3 Answers3

1

You are possibly approaching this backwards. Generally, you want to use compose to combine all the transformations and pass that composed function to the .then handler of a promise:

// assumes curried times and add functions
let tranformData = _.compose(square, times(2), add(3));

let foo = fetchNumberAsync() // returns a promise of 3
  .then(transformData)
  .catch(err => doSomethingWithError(err));

foo.then(console.log); // prints 144 to the console ((3 + 3) * 2) ** 2

Compare to the alternatives:

// clear but wasteful
let foo = fetchNumberAsync()
  .then(n => n + 3)
  .then(n => n * 2)
  .then(n => n ** 2)

or

// performant but opaque
let foo = fetchNumberAsync().then(n => ((n + 3) * 2) ** 2)

Using compose (especially with memoize) is a good middle road.

Jared Smith
  • 19,721
  • 5
  • 45
  • 83
1

Why forEach await can not work? You provide an async function to forEach, but inside the implementation of forEach function, it is not awaited. I guess the forEach is like that:

Array.prototype.forEach = function (func) {
    for (let item of this) {
        func(item);    // Note here, there is no "await" in front of it 
    }
}

Can I use promise function in reduce? No, you cannot same reason as above.

The alternative way via "co" to smooth your code is like the below:

let ret = await rv.copyDetailList.map(co(function * (item) {
    const v = yield convertCommonInfo(item);
    if (!item.errorId) {
        res.approveList.push(v);
    } else {
        res.rejectList.push(merge(v, {errorId: item.errorId, errorDesc: item.errorMsg}));
    }
}));
Ron
  • 6,037
  • 4
  • 33
  • 52
1

If your ultimate goal is to achieve a single traversal where each iteration runs concurrently, then there are various libraries available that provide an "asynchronous each" along the lines of what it sounds like you're looking for.

One such library is async-parallel, which offers a Parallel.each iterator that works nicely with async/await.

Using this library your code could look like this...

let res = {approveList: [], rejectList: [], errorId: rv.errorId, errorDesc: rv.errorDesc};
await Parallel.each(rv.copyDetailList, async (item) => {
    var v = await convertCommonInfo(item);
    if (!item.errorId)
        res.approveList.push(v);
    else
        res.rejectList.push(merge(v, {errorId: item.errorId, errorDesc: item.errorMsg}));
});
Dave Templin
  • 1,764
  • 1
  • 11
  • 5