所有async
函数都返回一个Promise。他们都是。
该Promise最终将使用您从异步函数返回的任何值来解决。
await
只阻止async
函数内部的执行。它不会阻塞函数之外的任何东西。从概念上讲,异步函数开始执行,一旦遇到await
指令,它会立即从函数返回一个未实现的Promise,外部执行世界得到该Promise并继续执行。
稍后,被await
ed的内部 promise 将被解析,然后函数内部其余部分的执行将继续。最终函数的内部将完成并返回一个值。这将触发解析从具有该返回值的函数返回的Promise。
仅供参考,您的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()
函数通常在您想要延迟Promise链的许多地方很有用。
感谢所有参与并为我提供洞察力的人。但我仍然很困惑我应该如何使用 await 和 async。
首先,大多数情况下,只有async
在需要await
在函数内部使用时才标记函数。
其次,当您有多个异步操作并且想要对它们进行排序时,最常使用await
(从async
函数内) - 通常是因为第一个操作提供的结果用作第二个操作的输入。await
当您只有一个异步操作时,您可以使用它,但与简单的.then()
.
以下是一些使用 的充分理由的示例async/await
:
对多个异步操作进行排序
想象一下,你有getFromDatabase()
,getTheUrl()
而且getTheContent()
都是异步的。如果有任何失败,您只想拒绝返回的第一个错误的Promise。
这是没有 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
版本的优势就越大。
自动捕获被拒绝的Promise和同步异常
正如我之前所说,async
函数总是返回一个Promise。他们还必须内置错误处理,自动将错误传播回返回的Promise。
不用说,如果您手动从async
函数返回一个Promise并且该Promise被拒绝,那么从该async
函数返回的Promise将被拒绝。
而且,如果您正在使用await
并且正在等待的任何Promise被拒绝,并且您.catch()
在Promise上没有 a 并且try/catch
周围没有 a ,那么函数返回的Promise将自动拒绝。所以,回到我们之前的例子中:
async function run(someArg) {
let key = await getFromDatabase(someArg);
let url = await getTheURL(key);
let content = await getTheContent(url);
// some final processing
return finalValue;
}
如果三个Promise中的任何一个被await
拒绝,那么该函数将短路(停止在函数中执行更多代码)并拒绝异步返回的Promise。因此,您可以免费获得这种形式的错误处理。
最后,一个async
函数还会为您捕获同步异常并将它们转换为被拒绝的Promise。
在一个像我们之前那样返回Promise的普通函数中:
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()
,他们都必须用 a 包围它try/catch
以捕获同步异常并使用 a.catch()
来捕获被拒绝的Promise:
try {
run(someArg).then(finalValue => {
console.log(finalValue);
}).catch(err => {
console.log(err);
});
} catch(e) {
console.log(err);
}
这是凌乱的,有点重复。但是,当run()
被声明时async
,它将永远不会同步抛出,因为任何同步异常都会自动转换为被拒绝的Promise,因此您可以确保在以这种方式编写时捕获所有可能的错误:
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 的库或者它是一个异步函数,我是否应该将所有函数调用从异步点追溯到应用程序的入口点,并在使它们异步之后在所有函数调用之前添加一个等待?
这个问题有点过于笼统,在一般情况下很难回答。以下是沿着这个大方向的一些想法:
一旦您尝试从函数返回的结果的任何部分A()
是异步的或使用任何异步操作来获取,该函数本身就是异步的。在纯 Javascript 中,您永远无法同步返回异步结果,因此您的函数必须使用异步方法来返回结果(Promise、回调、事件等)。
任何B()
调用异步函数的函数A()
也试图根据其获取的结果返回结果,A()
现在也是异步的,并且还必须使用异步机制将其结果传回。对于C()
调用B()
并需要将其结果传达给调用者的函数来说,这是正确的。因此,您可以说异步行为具有传染性。直到您到达调用链中不再需要返回结果的某个点之前,一切都必须使用异步机制来传达结果、错误和完成。
没有特别需要标记一个函数,async
除非您特别需要一个async
函数的好处之一,例如await
在该函数内部使用的能力或它提供的自动错误处理。您可以编写返回 Promise 的函数,而无需async
在函数声明中使用。所以,“不”我不会回到调用链上做所有事情async
。如果有特定的原因,我只会使函数异步。通常那个原因是我想使用await
在函数内部,但也有同步异常的自动捕获,这些异常会变成我之前描述的Promise拒绝。对于行为良好的代码,您通常不需要它,但有时对于行为不佳的代码或具有未定义行为的代码很有用。
await
也仅在有特定原因时使用。我不只是在每个返回Promise的函数上自动使用它。我已经在上面描述了使用它的原因。仍然可以使用.then()
justfine 来处理返回Promise的单个函数调用的结果。在某些情况下,是否要使用.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));