You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
内置执行器
Generator 函数的执行必须靠执行器,例如第三方的 co 模块;而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。async 函数会自动执行,遇到异步时会转移执行权直到异步完成,再继续执行后续的同步代码,输出最后结果。这完全不像 Generator 函数,需要调用 next 方法,或者用 co 模块,才能真正执行,得到最后结果。
asyncfunctionf(){try{awaitPromise.reject('出错了');}catch(e){}returnawaitPromise.resolve('hello world');}f().then(v=>console.log(v))// hello world
另一种方法是直接在 Promise 对象后面添加 catch 回调:
asyncfunctionf(){awaitPromise.reject('出错了').catch(e=>console.log(e));returnawaitPromise.resolve('hello world');}f().then(v=>console.log(v))// 出错了// hello world
参考文章:
目录
内容
async 函数是 Generator 函数的语法糖。两者差异主要体现在以下几点:
Generator 函数的执行必须靠执行器,例如第三方的 co 模块;而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。async 函数会自动执行,遇到异步时会转移执行权直到异步完成,再继续执行后续的同步代码,输出最后结果。这完全不像 Generator 函数,需要调用 next 方法,或者用 co 模块,才能真正执行,得到最后结果。
async 和 await,比起
*
和 yield,语义更清楚了。async 表示函数里有异步操作,或者表示当前函数被声明为了异步函数,await 表示紧跟在后面的表达式需要等待结果。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
async 函数的返回值会被包装为 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。后续代码获取 Promise 返回值后可以用 then 方法指定下一步的操作。
声明为 async 的函数,在执行的时候,一旦遇到 await 就会暂停执行后续代码,冻结和保存当前的变量和执行位置,函数执行上下文会弹出栈,将线程交由后面的代码执行,直到 await 后面的异步操作完成,将冻结的变量恢复并重新入栈,从上次暂停的位置继续执行后续代码。此外,async 函数最终会将返回值包装为一个 Promise 对象返回,我们可以使用 then 方法为其添加回调函数。该回调函数会在 async 函数内部所有异步代码执行后调用。
async 函数会将 return 的值包装为一个 Promise 对象返回。
async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return语句或者抛出错误才会提前发生状态变更。
await 后面允许是一个 Promise 对象 / 基础类型 / thenable 对象
正常情况下,await 命令后面是一个 Promise 对象,await 会等待 Promise 状态改变后返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。若 await 命令后面是一个 thenable 对象(即定义了then方法的对象),那么 await 会将其等同于 Promise 对象处理。
此外,任何一个 await 语句后面的 Promise 对象变为 reject 状态,都会中断整个 async 函数的执行,Promise.reject 的参数会被 catch 注册的回调函数接收到。
async 函数内部 Promise 状态变为 rejected 时,会导致函数直接返回错误信息,跳出函数体。实际上跳出循环是错误机制引发的,错误发生后,下一个执行就是将错误信息传递给最近的错误捕获机制,假设最近的错误捕获在函数体内,那么就不会跳出函数体,而是直接跳转到 catch 所在的代码块执行逻辑,其中错误代码到 catch 代码块之间的所有代码都被静默忽略,catch 捕获错误后会执行之后的代码。
假如我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个 await 放在 try...catch 结构里面,在内部就捕获错误信息,从而避免错误信息为了被外部错误机制捕获而跳出函数体。
另一种方法是直接在 Promise 对象后面添加 catch 回调:
try ... catch ...
中,可以避免某个 Promise 异步执行出错导致跳出 async 函数Promise.all()
。forEach 内的 await 命令会并发执行,因为 forEach 执行的是启动 async 函数,而不会等待内部的 await 返回结果
for 循环会等待循环体内的代码执行完毕后,进行下一次循环,因此它会等待内部的 await 结果,因此循环是继发执行的。
上面代码中,函数a内部运行了一个异步任务b()。当b()运行的时候,函数a()不会中断,而是继续执行。等到b()运行结束,可能a()早就运行结束了,b()所在的上下文环境已经消失了。如果b()或c()报错,错误堆栈将不包括a()。
现在将这个例子改成async函数。
上面代码中,b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()或c()报错,错误堆栈将包括a()。
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
回调函数
回调函数执行异步处理容易引发“回调地狱”
Promise
Promise 采用链式调用,一定程度上解决了回调地狱,但引入了许多 Promise 的 API,导致操作本身的语义不容易被看到。
Generator
Generator 找到了一种异步代码同步实现的书写方式,语义上比 Promise 更清晰,但是它必须引入一个自执行器来自动执行 Generator 函数。自执行器的引入就导致我们代码依赖于第三方库,此外,co 模块要求返回一个 Promise 对象,而且必须保证 yield语句后面的表达式,必须返回一个 Promise。
async
async 相较于 Generator 做了进一步改进,更加语义化、广泛化,同时它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户。
为什么要解决顶层 await ?
根据语法规格,我们知道现阶段 await 命令只能出现在 async 函数内部,否则都会报错。这也就意味着,在模块最外层,我们不能使用 await 命令,因为最外层是模块的作用域而非函数作用域,无法添加 async。
目前,有一个语法提案,允许在模块的顶层独立使用 await 命令。这个提案的目的,是借用 await 解决模块异步加载的问题。
模块异步加载
现存在一个文件,它内部向外暴露了一个异步操作的结果:
output 变量的值会在异步操作结束后返回,没结束时其他模块使用它,得到的是 undefined;
上述代码中,模块引入了 output 变量,如果 awaiting.js 里面的异步操作没执行完,加载进来的 output 的值就是 undefined。
为了让使用的模块能够在异步操作结束后才使用异步操作的结果变量,目前的解决方法,就是让原始模块输出一个 Promise 对象,从这个 Promise 对象判断异步操作有没有结束。
上面代码中,awaiting.js除了输出output,还默认输出一个 Promise 对象(async 函数立即执行后,返回一个 Promise 对象),其他模块使用时, 从这个 Promise 对象判断异步操作是否结束:
上面代码中,将awaiting.js对象的输出,放在promise.then()里面,这样就能保证异步操作完成以后,才去读取output。
通过 Promise 对象解决模块异步加载时存在的问题
其他模块调用模块异步加载的结果,需要遵守一个额外的使用协议,就是通过 Promise 对象来加载和获取最终的结果。一旦忘了要用 Promise 加载,只使用正常的加载方法,依赖这个模块的代码就可能出错。而且,这也容易导致这个依赖链上的所有模块都要使用 Promise 加载。
顶层的await命令,就是为了解决这个问题。它保证只有异步操作完成,模块才会输出值。
顶层 await 使用
顶层 await命令保证了模块在顶层调用 await 命令时不报错,同时只有等到异步操作完成,这个模块才会输出值。
此时,加载模块的写法如下。
其他模块使用异步模块加载的结果时,不再需要关心依赖模块内部是否存在异步操作,异步操作是否已经完成。因此上面代码的写法,与普通的模块加载完全一样。
模块的加载会等待依赖模块(上例是awaiting.js)的异步操作完成,才执行后面的代码,有点像暂停在那里。所以,它总是会得到正确的output,不会因为加载时机的不同,而得到不一样的值。
The text was updated successfully, but these errors were encountered: