使用 Promise.all 获取基于名称的结果的最佳 es6 方法
Best es6 way to get name based results with Promise.all
默认情况下,Promise.All([]) 函数 return 是一个基于数字的索引数组,其中包含每个承诺的结果。
var promises = [];
promises.push(myFuncAsync1()); //returns 1
promises.push(myFuncAsync1()); //returns 2
Promise.all(promises).then((results)=>{
//results = [0,1]
}
return 使用 Promise.all() 的命名结果索引的最佳普通方法是什么?
我尝试使用 Map,但它 returns 以这种方式生成数组:
[key1, value1, key2, value2]
更新:
我的问题似乎不清楚,这就是我不喜欢基于有序索引的原因:
- 维护起来很糟糕:如果你在代码中添加一个承诺,你可能不得不重写整个结果函数,因为索引可能有变化。
- 读起来很糟糕:
results[42]
(可以用下面jib的回答修复)
- 在动态上下文中不太可用:
var promises = [];
if(...)
promises.push(...);
else{
[...].forEach(... => {
if(...)
promises.push(...);
else
[...].forEach(... => {
promises.push(...);
});
});
}
Promise.all(promises).then((resultsArr)=>{
/*Here i am basically fucked without clear named results
that dont rely on promises' ordering in the array */
});
是这样的吗?
var promises = [];
promises.push(myFuncAsync1().then(r => ({name : "func1", result : r})));
promises.push(myFuncAsync1().then(r => ({name : "func2", result : r})));
Promise.all(promises).then(results => {
var lookup = results.reduce((prev, curr) => {
prev[curr.name] = curr.result;
return prev;
}, {});
var firstResult = lookup["func1"];
var secondResult = lookup["func2"];
}
ES6 支持解构,所以如果你只是想命名结果你可以这样写:
var myFuncAsync1 = () => Promise.resolve(1);
var myFuncAsync2 = () => Promise.resolve(2);
Promise.all([myFuncAsync1(), myFuncAsync2()])
.then(([result1, result2]) => console.log(result1 +" and "+ result2)) //1 and 2
.catch(e => console.error(e));
现在可以在 Firefox 和 Chrome 中使用。
一个很好的解决方案是使用异步等待。不完全是你问的 ES6,而是 ES8!但由于 Babel 完全支持它,我们开始吧:
您可以通过使用 async
/await
来避免仅使用数组索引,如下所示。
这个 async
函数允许您在函数内部使用 await
关键字,将其放在 promise 之前,从而让您真正停止其中的代码。正如 async
函数在尚未解决的承诺上遇到 await
一样,该函数立即 return 成为待处理的承诺。此 returned 承诺会在函数 actually 稍后完成后立即解析。该函数只会在先前等待的 promise 得到解决时恢复,在此期间它将整个 await Promise
语句解析为该 Promise 的 return 值,允许您将其放入变量中。这有效地允许您在不阻塞线程的情况下暂停代码。一般来说,这是处理 JavaScript 中的异步内容的好方法,因为它使您的代码更符合时间顺序,因此更容易推理:
async function resolvePromiseObject(promiseObject) {
await Promise.all(Object.values(promiseObject));
const ret = {};
for ([key, value] of Object.entries(promiseObject)) {
// All these resolve instantly due to the previous await
ret[key] = await value;
};
return ret;
}
与 ES5 以上的任何东西一样:请确保正确配置 Babel,以便使用旧浏览器的用户可以 运行 您的代码而不会出现问题。只要您的 babel 配置正确,您甚至可以让 async await 在 IE11 上完美运行。
关于@kragovip 的回答,您想要避免的原因如下所示:
“...习惯等待所有网络和 I/O 呼叫真的很容易。
但是,连续多次使用它时要小心,因为 await 关键字会停止执行它之后的所有代码。 (就像在同步代码中一样)"
不好的例子(不要跟进)
async function processData() {
const data1 = await downloadFromService1();
const data2 = await downloadFromService2();
const data3 = await downloadFromService3();
...
}
"也绝对不需要等待第一个请求的完成,因为 none 其他请求取决于其结果。
我们希望并行发送请求并等待所有请求同时完成。这就是异步事件驱动编程的强大之处。
要解决这个问题,我们可以使用 Promise.all() 方法。我们从对变量的异步函数调用中保存 Promises,将它们组合到一个数组中并一次等待它们。"
改为
async function processData() {
const promise1 = downloadFromService1();
const promise2 = downloadFromService2();
const promise3 = downloadFromService3();
const allResults = await Promise.all([promise1, promise2, promise3]);
如果您不想修改结果对象的格式,这里有一个辅助函数,它允许为每个条目分配一个名称以便以后访问它。
const allNamed = (nameToPromise) => {
const entries = Object.entries(nameToPromise);
return Promise.all(entries.map(e => e[1]))
.then(results => {
const nameToResult = {};
for (let i = 0; i < results.length; ++i) {
const name = entries[i][0];
nameToResult[name] = results[i];
}
return nameToResult;
});
};
用法:
var lookup = await allNamed({
rootStatus: fetch('https://whosebug.com/').then(rs => rs.status),
badRouteStatus: fetch('https://whosebug.com/badRoute').then(rs => rs.status),
});
var firstResult = lookup.rootStatus; // = 200
var secondResult = lookup.badRouteStatus; // = 404
如果您使用的是打字稿,您甚至可以使用 keyof
构造指定输入键和结果之间的关系:
type ThenArg<T> = T extends PromiseLike<infer U> ? U : T;
export const allNamed = <
T extends Record<string, Promise<any>>,
TResolved extends {[P in keyof T]: ThenArg<T[P]>}
>(nameToPromise: T): Promise<TResolved> => {
const entries = Object.entries(nameToPromise);
return Promise.all(entries.map(e => e[1]))
.then(results => {
const nameToResult: TResolved = <any>{};
for (let i = 0; i < results.length; ++i) {
const name: keyof T = entries[i][0];
nameToResult[name] = results[i];
}
return nameToResult;
});
};
默认情况下,Promise.All([]) 函数 return 是一个基于数字的索引数组,其中包含每个承诺的结果。
var promises = [];
promises.push(myFuncAsync1()); //returns 1
promises.push(myFuncAsync1()); //returns 2
Promise.all(promises).then((results)=>{
//results = [0,1]
}
return 使用 Promise.all() 的命名结果索引的最佳普通方法是什么?
我尝试使用 Map,但它 returns 以这种方式生成数组:
[key1, value1, key2, value2]
更新:
我的问题似乎不清楚,这就是我不喜欢基于有序索引的原因:
- 维护起来很糟糕:如果你在代码中添加一个承诺,你可能不得不重写整个结果函数,因为索引可能有变化。
- 读起来很糟糕:
results[42]
(可以用下面jib的回答修复) - 在动态上下文中不太可用:
var promises = [];
if(...)
promises.push(...);
else{
[...].forEach(... => {
if(...)
promises.push(...);
else
[...].forEach(... => {
promises.push(...);
});
});
}
Promise.all(promises).then((resultsArr)=>{
/*Here i am basically fucked without clear named results
that dont rely on promises' ordering in the array */
});
是这样的吗?
var promises = [];
promises.push(myFuncAsync1().then(r => ({name : "func1", result : r})));
promises.push(myFuncAsync1().then(r => ({name : "func2", result : r})));
Promise.all(promises).then(results => {
var lookup = results.reduce((prev, curr) => {
prev[curr.name] = curr.result;
return prev;
}, {});
var firstResult = lookup["func1"];
var secondResult = lookup["func2"];
}
ES6 支持解构,所以如果你只是想命名结果你可以这样写:
var myFuncAsync1 = () => Promise.resolve(1);
var myFuncAsync2 = () => Promise.resolve(2);
Promise.all([myFuncAsync1(), myFuncAsync2()])
.then(([result1, result2]) => console.log(result1 +" and "+ result2)) //1 and 2
.catch(e => console.error(e));
现在可以在 Firefox 和 Chrome 中使用。
一个很好的解决方案是使用异步等待。不完全是你问的 ES6,而是 ES8!但由于 Babel 完全支持它,我们开始吧:
您可以通过使用 async
/await
来避免仅使用数组索引,如下所示。
这个 async
函数允许您在函数内部使用 await
关键字,将其放在 promise 之前,从而让您真正停止其中的代码。正如 async
函数在尚未解决的承诺上遇到 await
一样,该函数立即 return 成为待处理的承诺。此 returned 承诺会在函数 actually 稍后完成后立即解析。该函数只会在先前等待的 promise 得到解决时恢复,在此期间它将整个 await Promise
语句解析为该 Promise 的 return 值,允许您将其放入变量中。这有效地允许您在不阻塞线程的情况下暂停代码。一般来说,这是处理 JavaScript 中的异步内容的好方法,因为它使您的代码更符合时间顺序,因此更容易推理:
async function resolvePromiseObject(promiseObject) {
await Promise.all(Object.values(promiseObject));
const ret = {};
for ([key, value] of Object.entries(promiseObject)) {
// All these resolve instantly due to the previous await
ret[key] = await value;
};
return ret;
}
与 ES5 以上的任何东西一样:请确保正确配置 Babel,以便使用旧浏览器的用户可以 运行 您的代码而不会出现问题。只要您的 babel 配置正确,您甚至可以让 async await 在 IE11 上完美运行。
关于@kragovip 的回答,您想要避免的原因如下所示:
“...习惯等待所有网络和 I/O 呼叫真的很容易。
但是,连续多次使用它时要小心,因为 await 关键字会停止执行它之后的所有代码。 (就像在同步代码中一样)"
不好的例子(不要跟进)
async function processData() {
const data1 = await downloadFromService1();
const data2 = await downloadFromService2();
const data3 = await downloadFromService3();
...
}
"也绝对不需要等待第一个请求的完成,因为 none 其他请求取决于其结果。
我们希望并行发送请求并等待所有请求同时完成。这就是异步事件驱动编程的强大之处。
要解决这个问题,我们可以使用 Promise.all() 方法。我们从对变量的异步函数调用中保存 Promises,将它们组合到一个数组中并一次等待它们。"
改为
async function processData() {
const promise1 = downloadFromService1();
const promise2 = downloadFromService2();
const promise3 = downloadFromService3();
const allResults = await Promise.all([promise1, promise2, promise3]);
如果您不想修改结果对象的格式,这里有一个辅助函数,它允许为每个条目分配一个名称以便以后访问它。
const allNamed = (nameToPromise) => {
const entries = Object.entries(nameToPromise);
return Promise.all(entries.map(e => e[1]))
.then(results => {
const nameToResult = {};
for (let i = 0; i < results.length; ++i) {
const name = entries[i][0];
nameToResult[name] = results[i];
}
return nameToResult;
});
};
用法:
var lookup = await allNamed({
rootStatus: fetch('https://whosebug.com/').then(rs => rs.status),
badRouteStatus: fetch('https://whosebug.com/badRoute').then(rs => rs.status),
});
var firstResult = lookup.rootStatus; // = 200
var secondResult = lookup.badRouteStatus; // = 404
如果您使用的是打字稿,您甚至可以使用 keyof
构造指定输入键和结果之间的关系:
type ThenArg<T> = T extends PromiseLike<infer U> ? U : T;
export const allNamed = <
T extends Record<string, Promise<any>>,
TResolved extends {[P in keyof T]: ThenArg<T[P]>}
>(nameToPromise: T): Promise<TResolved> => {
const entries = Object.entries(nameToPromise);
return Promise.all(entries.map(e => e[1]))
.then(results => {
const nameToResult: TResolved = <any>{};
for (let i = 0; i < results.length; ++i) {
const name: keyof T = entries[i][0];
nameToResult[name] = results[i];
}
return nameToResult;
});
};