让我把这段代码翻译成几个不同的版本。首先是原始版本,但删除了额外的工作:
const fetch = require('node-fetch')
const URL = "https://swapi.co/api/people/";
async function fetchPerson(url){
const result = await fetch(url);
return await result.json();
}
async function printNames() {
const person1 = await fetchPerson(URL+1);
const person2 = await fetchPerson(URL+2);
console.log([person1.name, person2.name]);
}
try {
await printNames();
} catch(error) {
console.error(error);
}
上面的代码等同于您发布的原始代码。现在为了更好地理解这里发生了什么,让我们把它翻译成完全相同的代码 pre-async/await。
const fetch = require('node-fetch')
const URL = "https://swapi.co/api/people/";
function fetchPerson(url){
return fetch(url).then((result) => {
return result.json();
});
}
function printNames() {
let results = [];
return fetchPerson(URL+1).then((person1) => {
results.push(person1.name);
return fetchPerson(URL+2);
}).then((person2) => {
results.push(person2.name);
console.log(results);
});
}
printNames().catch((error) => {
console.error(error);
});
上面的代码和你贴的原始代码是等价的,我只是做了JS翻译器会做的额外工作。我觉得这让它更清楚一点。按照上面的代码,我们将执行以下操作:
- 致电
printNames()
-
printNames() 将请求 person1 并等待响应。
-
printNames() 将请求 person2 并等待响应。
-
printNames() 将打印结果
您可以想象,这可以改进。让我们同时请求两个人。我们可以用下面的代码做到这一点
const fetch = require('node-fetch')
const URL = "https://swapi.co/api/people/";
function fetchPerson(url){
return fetch(url).then((result) => {
return result.json();
});
}
function printNames() {
return Promise.all([fetchPerson(URL+1).then((person1) => person1.name), fetchPerson(URL+2).then((person2) => person2.name)]).then((results) => {
console.log(results);
});
}
printNames().catch((error) => {
console.error(error);
});
此代码不等同于发布的原始代码。我们现在不是串行执行所有操作,而是并行获取不同的用户。现在我们的代码执行以下操作
- 致电
printNames()
-
printNames()会
- 发送
person1的请求
- 发送
person2的请求
-
printNames() 将等待两个请求的响应
-
printNames() 将打印结果
这个故事的寓意是async/await 并不是在所有情况下都可以替代 Promise,它是一种语法糖,可以使处理 Promises 的非常具体的方式变得更容易。如果您希望/可以并行执行任务,请不要在每个单独的 Promise 上使用 async/await。您可以改为执行以下操作:
const fetch = require('node-fetch')
const URL = "https://swapi.co/api/people/";
async function fetchPerson(url){
const result = await fetch(url);
return result.json();
}
async function printNames() {
const [ person1, person2 ] = await Promise.all([
fetchPerson(URL+1),
fetchPerson(URL+2),
]);
console.log([person1.name, person2.name]);
}
printNames().catch((error) => {
console.error(error);
});
免责声明
printNames()(和其他所有东西)并没有真正等待任何东西。它将继续执行 I/O 之后未出现在 I/O 回调中的任何和所有代码。 await 只是创建剩余代码的回调,当 I/O 完成时调用该回调。例如,下面的代码 sn -p 不会做你所期望的。
const results = Promise.all([promise1, promise2]);
console.log(results); // Gets executed immediately, so the Promise returned by Promise.all() is printed, not the results.
串行与并行
关于我在 cmets 中与 OP 的讨论,我还想添加对串行与并行工作的描述。我不确定你对不同的概念有多熟悉,所以我会给出一个相当抽象的描述。
首先,我认为 JS 不支持并行操作在同一个 JS 环境中是谨慎的。具体来说,与我可以启动一个线程以并行执行任何工作的其他语言不同,JS 只能(似乎)在其他事物正在执行工作(I/O)时并行执行工作。
话虽如此,让我们从串行与并行外观的简单描述开始。想象一下,如果你愿意,为 4 个不同的班级做作业。完成每个班级的家庭作业所需的时间如下表所示。
Class | Time
1 | 5
2 | 10
3 | 15
4 | 2
当然,你所做的工作会连续发生,看起来像
You_Instance1: doClass1Homework() -> doClass2Homework() -> doClass3Homework() -> doClass4Homework()
连续完成作业需要 32 个单位的时间。但是,如果您可以将自己分成 4 个不同的实例,那不是很好吗?如果是这样的话,你可以为你的每个类都有一个你自己的实例。这可能看起来像
You_Instance1: doClass1Homework()
You_Instance2: doClass2Homework()
You_Instance3: doClass3Homework()
You_Instance4: doClass4Homework()
并行工作,您现在可以在 15 个单位时间内完成作业!这不到一半的时间。
“但是等等,”你说,“将自己分成多个实例来完成我的作业肯定有一些缺点,否则每个人都会这样做。”
你是对的。将自己拆分为多个实例会产生一些开销。假设分裂自己需要深度冥想和出体体验,这需要 5 个单位的时间。现在完成你的作业看起来像这样:
You_Instance1: split() -> split() -> doClass1Homework()
You_Instance2: split() -> doClass2Homework()
You_Instance3: doClass3Homework()
You_Instance4: doClass4Homework()
现在完成作业需要 25 个单位,而不是 15 个单位的时间。这仍然比自己完成所有作业要便宜。
总结(如果您了解串行与并行执行,请跳过此处)
这可能是一个愚蠢的例子,但这正是串行与并行执行的样子。并行执行的主要优点是您可以同时执行多个长时间运行的任务。由于多名工作人员同时在做某事,因此工作完成得更快。
但是,也有缺点。其中最大的两个是开销和复杂性。无论您使用什么环境/语言,并行执行代码都不是免费的。在 JS 中,并行执行可能非常昂贵,因为这是通过向服务器发送请求来实现的。根据各种因素,往返可能需要 10 到 100 毫秒。这对于现代计算机来说非常慢。这就是为什么通常为长时间运行的进程(完成作业)或无法避免的情况(从磁盘或服务器加载数据)保留并行执行。
另一个主要缺点是增加了复杂性。协调并行发生的多个任务可能很困难(Dining Philosophers、Consumer-Producer Problem、Starvation、Race Conditions)。但是在 JS 中,复杂性也来自对代码的理解(Callback Hell,理解什么时候执行)。如上所述,在异步代码之后发生的一组指令不会等到异步代码完成后才执行(除非它发生在回调中)。
如何让我自己的多个实例在 JS 中完成作业?
有几种不同的方法可以做到这一点。一种方法是设置 4 个不同的服务器。我们称它们为class1Server、class2Server、class3Server 和class4Server。现在要让这些服务器并行完成您的作业,您可以执行以下操作:
Promise.all([
startServer1(),
startServer2(),
startServer3(),
startServer4()
]).then(() => {
console.log("Homework done!");
}).catch(() => {
console.error("One of me had trouble completing my homework :(");
});
Promise.all() 返回一个 Promise,它要么在所有 Promise 都解决时解决,要么在其中一个被拒绝时拒绝。