JavaScript 数组。用异步/等待进行简化

似乎在使用. 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()吗?

104221 次浏览

问题是您的累加器值是承诺-它们是 async function的返回值。为了获得顺序计算(以及除了最后一次迭代之外的所有迭代) ,您需要使用

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

也就是说,对于 async/await,我一般推荐 使用普通循环代替数组迭代方法,它们性能更好,通常也更简单。

您可以将整个 map/reduce 迭代器块封装到它们自己的 Prose.decision 中,然后等待完成。但是,问题在于累加器并不包含每次迭代所期望的结果数据/对象。由于内部的异步/等待/承诺链,累加器将是实际的承诺本身,可能还没有解决自己,尽管使用等待关键字之前,你的商店调用(这可能会导致你认为,迭代实际上不会返回,直到调用完成和累加器更新。

虽然这不是最优雅的解决方案,但您可以选择将 资料对象变量移出作用域,并将其指定为 ,以便发生适当的绑定和突变。然后在解析异步/等待/承诺调用时,从迭代器内部更新这个数据对象。

/* 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);

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

我还想提一下我的一个图书馆,名为 等等 Js

它可以让你毫不费力地使用 reducemapfilter这样的函数和 async / 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: { ... } }

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

[没有解决作战计划的确切问题,而是关注在这里登陆的其他人。]

当您需要前面步骤的结果才能处理下一个步骤时,通常使用 Reduce。在这种情况下,你可以把承诺串在一起:

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

下面是一个使用 fs.mise.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 ()

下面是如何使异步减少:

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

有时候最好的办法就是把两个版本的代码放在一起,同步和异步:

同步版本:

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


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

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

青鸟的另一个经典选项

const promise = require('bluebird');


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


// Expected to product sum 6

目前公认的答案是建议使用 Promise.all()而不是 async reduce。但是,这并不具有与 async reduce相同的行为,并且只与您希望异常立即停止所有迭代的情况相关,而这种情况并不总是如此。

此外,在该答案的注释中建议您始终等待累加器作为减少器中的第一个语句,因为否则您可能面临未处理的承诺拒绝的风险。海报还说,这是观察所要求的,但事实并非如此。相反,他只是想知道什么时候一切都完成了。为了知道你确实需要做 await acc,但这可能是在减速器的任何一点。

const reducer = async(acc, key) => {
const response = await api(item);


return {
...await acc, // <-- this would work just as well for OP
[key]: response,
}
}
const result = await ['a', 'b', 'c', 'd'].reduce(reducer, {});
console.log(result); // <-- Will be the final result

如何安全使用 async减少

也就是说,以这种方式使用减速器意味着你需要保证它不会抛出,否则你将得到“未处理的承诺拒绝”。通过使用 try-catch来确保这一点是完全可能的,catch块返回累加器(可以选择为失败的 API 调用提供记录)。

const reducer = async (acc, key) => {
try {
data = await doSlowTask(key);
return {...await acc, [key]: data};
} catch (error) {
return {...await acc, [key]: {error}};
};
}
const result = await ['a', 'b', 'c','d'].reduce(reducer, {});

Promise.allSettled的不同之处 通过使用 Promise.allSettled,您可以接近 async reduce的行为(带有错误捕捉)。然而,这种方法使用起来很笨拙: 如果您想要简化为一个对象,就需要在它之后添加另一个同步 reduce。

对于 Promise.allSettled + 常规 reduce,理论上的时间复杂度也更高,尽管可能只有很少的用例会有所不同。async reduce可以从第一个项目完成的那一刻开始积累,而 Promise.allSettled之后的 reduce被阻塞,直到所有的承诺都得到兑现。这在循环处理大量元素时可能会有所不同。

const responseTime = 200; //ms
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}


const api = async (key) => {
console.log(`Calling API for ${ key }`);
// Boz is a slow endpoint.
await sleep(key === 'boz' ? 800 : responseTime);
console.log(`Got response for ${ key }`);


if (key === 'bar') throw new Error(`It doesn't work for ${ key }`);


return {
[key]: `API says ${ key }`,
};
};


const keys = ['foo', 'bar', 'baz', 'buz', 'boz'];


const reducer = async (acc, key) => {
let data;
try {
const response = await api(key);
data = {
apiData: response
};
} catch (e) {
data = {
error: e.message
};
}


// OP doesn't care how this works, he only wants to know when the whole thing is ready.
const previous = await acc;
console.log(`Got previous for ${ key }`);


return {
...previous,
[key]: {
...data
},
};
};
(async () => {
const start = performance.now();
const result = await keys.reduce(reducer, {});
console.log(`After ${ performance.now() - start }ms`, result); // <-- OP wants to execute things when it's ready.
})();

使用 Promise.allSettled检查执行顺序:

const responseTime = 200; //ms
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}


const api = async (key) => {
console.log(`Calling API for ${ key }`);
// Boz is a slow endpoint.
await sleep(key === 'boz' ? 800 : responseTime);
console.log(`Got response for ${ key }`);


if (key === 'bar') throw new Error(`It doesn't work for ${ key }`);


return {
key,
data: `API says ${ key }`,
};
};


const keys = ['foo', 'bar', 'baz', 'buz', 'boz'];


(async () => {
const start = performance.now();
const apiResponses = await Promise.allSettled(keys.map(api));
const result = apiResponses.reduce((acc, {status, reason, value}) => {
const {key, data} = value || {};
console.log(`Got previous for ${ key }`);
return {
...acc,
[key]: status === 'fulfilled' ? {apiData: data} : {error: reason.message},
};
}, {});
console.log(`After ${ performance.now() - start }ms`, result); // <-- OP wants to execute things when it's ready.
})();

对于打印脚本,上一个值和初始值必须相同。

const data = await array.reduce(async (accumP: Promise<Tout>, curr<Tin>) => {
const accum: Tout = await accumP;
    

doSomeStuff...


return accum;


}, Promise<Tout>.resolve({} as Tout);

我的 typescript.reduce的解决方案

多亏了这个人 Https://dev.to/arnaudcourtecuisse/comment/1el22

const userOrders = await existUsersWithName.reduce(
async (promise, existUserAndName) => {
const acc = await promise;


const {user, name} = existUserAndName;


// My async function
acc[user] = await this.users.getOrders(name);


return promise;
},
<Promise<Record<string, string[] | undefined>>>{}
);