所有async 函数都返回一个承诺。全部。
这个承诺最终会用你从异步函数返回的任何值来解决。
await 仅阻止 async 函数内部的执行。它不会阻止函数之外的任何内容。从概念上讲,异步函数开始执行,一旦遇到await 指令,它会立即从函数返回一个未实现的承诺,外部执行世界会得到该承诺并继续执行。
稍后,awaited 的内部承诺将解析,然后函数内部的其余部分将继续执行。最终,函数的内部将完成并返回一个值。这将触发使用该返回值解析从函数返回的承诺。
仅供参考,您的 load() 函数中有很多多余的东西。你可以从这里改变它:
async function load() {
const data = await new Promise(resolve => {
setTimeout(() => resolve([1, 2, 3]), 10);
}).then(data => data.map(i => i * 10));
console.log(`Data inside the function: ${JSON.stringify(data)}`);
return data;
}
到这里:
function load() {
return new Promise(resolve => {
setTimeout(() => resolve([1, 2, 3]), 10);
}).then(data => data.map(i => i * 10));
}
然后,像这样使用它:
load().then(result => {
console.log(result);
});
或者,我更喜欢将手动创建的 Promise 封装在自己的函数中,如下所示:
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
}
function load() {
return delay(10, [1, 2, 3]).then(data => data.map(i => i * 10));
}
而且,事实证明,这个小 delay() 函数通常在很多你想延迟承诺链的地方很有用。
感谢所有参与并为我提供见解的人。但是我仍然很困惑我应该如何使用 await 和 async。
首先,大多数情况下,如果您需要在函数内部使用await,您只需标记函数async。
其次,当您有多个异步操作并且想要对它们进行排序时,您最常使用await(来自async 函数) - 通常是因为第一个操作提供的结果用作第二个操作的输入.当您只有一个异步操作时,您可以使用 await,但与简单的 .then() 相比,它并没有真正提供太多优势。
这里有几个使用 async/await 的充分理由示例:
对多个异步操作进行排序
假设您有 getFromDatabase()、getTheUrl() 和 getTheContent(),它们都是异步的。如果有任何失败,您可能只想用第一个错误拒绝返回的承诺。
这是没有 async/await 的情况:
function run() {
return getFromDatabase(someArg).then(key => {
return getTheURL(key);
}).then(url => {
return getTheContent(url);
}).then(content => {
// some final processing
return finalValue;
});
}
这是async/await 的样子:
async function run(someArg) {
let key = await getFromDatabase(someArg);
let url = await getTheURL(key);
let content = await getTheContent(url);
// some final processing
return finalValue;
}
在这两种情况下,函数都会返回一个以 finalValue 解析的 Promise,因此调用者使用相同的这两个实现:
run(someArg).then(finalValue => {
console.log(finalValue);
}).catch(err => {
console.log(err);
});
但是,您会注意到async/await 实现具有更多的序列化、同步外观,并且看起来更像非异步代码。许多人发现这更容易编写、更容易阅读和更容易维护。步骤之间的处理越多,包括分支,async/await 版本的优势就越多。
自动捕获被拒绝的承诺和同步异常
正如我之前所说,async 函数总是返回一个承诺。他们还必须内置错误处理,自动将错误传播回返回的承诺。
不用说,如果您手动从 async 函数返回一个承诺并且该承诺拒绝,那么从 async 函数返回的承诺将被拒绝。
而且,如果您正在使用 await 并且您正在等待的任何承诺都被拒绝,并且您的承诺上没有 .catch() 并且周围没有 try/catch,那么该功能的承诺退货将自动拒绝。所以,回到我们之前的例子:
async function run(someArg) {
let key = await getFromDatabase(someArg);
let url = await getTheURL(key);
let content = await getTheContent(url);
// some final processing
return finalValue;
}
如果被awaited 拒绝的三个promise 中的任何一个被拒绝,那么函数将短路(停止执行函数中的任何更多代码)并拒绝异步返回的promise。因此,您可以免费获得这种形式的错误处理。
最后,async 函数还会为您捕获同步异常并将它们转换为被拒绝的承诺。
在一个正常的函数中,如我们之前所说的那样:
function run() {
return getFromDatabase(someArg).then(key => {
return getTheURL(key);
}).then(url => {
return getTheContent(url);
}).then(content => {
// some final processing
return finalValue;
});
}
如果getFromDatabase()抛出同步异常(可能是因为someArg无效而触发),那么run()这个整体函数会同步抛出。这意味着调用者要从run() 捕获所有可能的错误,他们必须用try/catch 包围它以捕获同步异常并使用.catch() 来捕获被拒绝的promise:
try {
run(someArg).then(finalValue => {
console.log(finalValue);
}).catch(err => {
console.log(err);
});
} catch(e) {
console.log(err);
}
这很混乱而且有点重复。但是,当 run() 被声明为 async 时,它永远不会同步抛出,因为任何同步异常都会自动转换为被拒绝的承诺,因此您可以确保在以这种方式编写时捕获所有可能的错误:
async function run(someArg) {
let key = await getFromDatabase(someArg);
let url = await getTheURL(key);
let content = await getTheContent(url);
// some final processing
return finalValue;
}
// will catch all possible errors from run()
run(someArg).then(finalValue => {
console.log(finalValue);
}).catch(err => {
console.log(err);
});
我应该总是用 await 调用我的所有函数吗?
首先,您只能将await 与返回promise 的函数一起使用,因为await 如果该函数不返回promise 则没有用处(如果不需要,只会增加代码的混乱)。
其次,您是否使用await 取决于调用函数的上下文(因为您必须在async 函数中才能使用await 以及逻辑流程以及它是否受益于是否使用await。
用 await 没意义的地方
async function getKey(someArg) {
let key = await getFromDatabase(someArg);
return key;
}
这里的await 没有做任何有用的事情。您没有对多个异步操作进行排序,也没有对返回值进行任何处理。你可以通过直接返回 Promise 来完成完全相同的代码:
async function getKey(someArg) {
return getFromDatabase(someArg);
}
而且,如果您知道 getFromDatabase() 永远不会同步抛出,您甚至可以从声明中删除 async:
function getKey(someArg) {
return getFromDatabase(someArg);
}
假设我正在编写由多个文件中的多个函数组成的代码。如果我最终使用了一个返回 Promise 的库或者它是一个异步函数,我是否应该将我的所有函数调用从异步点追溯到应用程序的入口点,并在所有函数调用之前添加一个 await 在使它们异步之后?
这个问题有点太笼统了,在一般情况下很难回答。以下是沿着这个大方向的一些想法:
-
一旦您尝试从函数A() 返回的结果的任何部分是异步的或使用任何异步操作来获取,函数本身就是异步的。在纯 Javascript 中,您永远无法同步返回异步结果,因此您的函数必须使用异步方法来返回结果(承诺、回调、事件等)。
-
任何调用异步函数A() 的函数B() 也尝试根据从A() 获得的结果返回结果,现在也是异步的,并且还必须使用异步机制将其结果传回。对于调用B() 并且需要将其结果返回给调用者的函数C() 来说,情况就是如此。因此,您可以说异步行为具有传染性。在您到达调用链中不再需要返回结果的某个点之前,一切都必须使用异步机制来传达结果、错误和完成。
-
没有特别需要标记函数async,除非您特别需要async 函数的好处之一,例如能够在该函数中使用await 或它提供的自动错误处理。您可以编写返回 Promise 的函数,而无需在函数声明中使用 async。所以,“不”我不会回到调用链上,让一切都变成async。如果有特定原因,我只会使函数异步。通常这个原因是我想在函数内部使用await,但也有同步异常的自动捕获,这些异常会变成我之前描述的承诺拒绝。对于表现良好的代码,您通常不需要它,但有时它对于表现不佳的代码或具有未定义行为的代码很有用。
-
await 也仅在有特定原因时使用。我不只是在每个返回承诺的函数上自动使用它。我已经描述了使用它的上述原因。仍然可以使用.then() 来处理返回承诺的单个函数调用的结果。在某些情况下,使用 .then() 还是 await 只是个人风格问题,没有特别的理由必须使用其中一种方式。
或者也许我应该养成使用 await 调用所有函数的习惯,无论它们是否异步?
绝对不是!首先,您要做的最后一件事是采用完全同步的代码,并不必要地使其异步,甚至使其看起来异步。与同步代码相比,异步代码(即使使用async 和await)编写、调试、理解和维护更复杂,因此您永远不想通过添加async/await 将同步代码不必要地变成异步代码:
例如,你永远不会这样做:
async function random(min, max) {
let r = await Math.random();
return Math.floor((r * (max - min)) + min);
}
首先,这是一个完美的同步操作,可以这样编码:
function random(min, max) {
let r = Math.random();
return Math.floor((r * (max - min)) + min);
}
其次,第一个 async 实现使函数很难使用,因为它现在有一个异步结果:
random(1,10).then(r => {
console.log(r);
});
而不仅仅是简单的同步使用:
console.log(random(1,10));