异步编程
Promise
在浏览器的事件循环中,总是先执行同步任务,然后执行异步任务,在异步任务中,回调函数被保存在了多级队列当中,不过可以简单的认为任务被分为两种队列,宏任务队列和微任务队列。每次执行异步任务时,总是先将微队列中的任务执行完毕之后然后再执行宏任务。
new Promise((s) => s()) // p0 f0
.then((res) => "") // p1 f1
.then((res) => Promise.resolve()) // p2 f2 pnew
.then((res) => ""); // p3 f3
构造函数
构造函数的执行:
先创建一个 Promise 实例,pending 状态
jsnew Promise((s, r) => {});
立即执行内部的这个函数,当内部调用
s
时,完成这个 Promise;如果调用r
时,拒绝这个 Promise。如果内部函数没有调用s
或者r
,那么这个 Promise 将一直处于 pending 状态,如果内部函数抛出了异常,那么这个 Promise 将被拒绝。
then 函数——完成时进队
对于一个 Promise,可以调用其 then 方法,并传入一个回调函数。此时其执行顺序为:
- 新建一个新的 Promise(p1),其状态取决于其回调函数(f1)是否执行完毕;
- 什么时候开始执行回调函数呢?在上一个回调函数完成时,将该回调函数放入微队列中进行等待。此处强调一个非常重要的点,就是当调用 then 方法传入了一个回调函数的时候,回调函数还没有进入微队列中,回调函数进入微队列的时机是在当前被 then 的这个 Promise 完成的时候。并且就算进了微队列,也不是说回调函数会立即执行,它还需要进行排队才会执行。
- 如果回调函数(f2)返回了一个新的 Promise(pnew),那么此时该 Promise(p2)将不会被完成,同时等待该 Promise 的回调函数(f3)不会在此时进队。取而代之的是,一个匿名的回调函数
() => pnew.then(() => '完成p2')
进入了队列当中,那么当执行了该匿名回调之后,() => '完成p2'
进队,等待 p2 完成时,f3 才会进队,换言之,f3 进队的时机将被推迟两个回调函数。
异步函数
异步函数,async function(){}
,是一个一定返回 Promise 的函数,它会将函数返回值包裹在一个 Promise 中。在异步函数中可以使用一个关键字 await,在 await 之前的所有逻辑将同步执行,但是在 await 之后的代码将被推到异步队列中进行执行。如果 await 关键字的右值是一个 Promise,那么这个 Promise 会被解包,直至 Promise 完成时,等待完成后才会进行 await 之后的逻辑,这也就是为什么 await 之后的代码会被推到异步队列中的原因。异步函数允许使用近似于同步函数的语法来声明 Promise,是一个 Promise 的语法糖,甚至是上位替代。捕获错误不使用.catch()语法,而是使用 try catch 语句,更加符合同步函数的语法。
异步生成器函数,将生成器函数和异步函数结合起来,很大程度上缓解了 JS 因为单线程限制而被大型任务阻塞的弊病。在异步生成器函数中,可以同时使用 await 和 yield 关键字,将大型任务进行异步解决和灵活拆分。
错误捕获
在同步代码中可以使用 try catch 来捕获错误,但在异步代码中的错误将无法被捕获。
相对同步
一段代码的所有顶层逻辑,往往在同一次事件循环中,在该次事件循环中,所有的在该次循环中的顶层代码均可以使用顶层 try-catch 语句进行捕获。
而如果在顶层中生成了新的函数,并且这个函数将会被稍后调用,而不是同步在顶层进行调用,那么,该函数的错误将无法被顶层 try-catch 捕获,从而产生游离的错误源,使得错误难以追踪。
所以,需要注意的是,在所有异步调用的函数或者回调函数处需要添加 try-catch,为所有的 Promise 添加 catch 逻辑。
全局回调函数
- DOM 事件回调函数
- 定时器回调函数
- 网络请求回调函数
- IO 操作回调函数
异步函数
Promise 的 then 回调函数。不过要澄清的是,new Prosmise 的时候,传入的回调函数是同步执行的,并且是由 Promise 构造函数内部进行捕获。
当 then 的回调函数出错后,Promise 的状态将被拒绝,并且拒绝的原因是回调函数抛出的错误,错误状态将沿着 then 的链条被传染给下游的 Promise,只要下游的任何一个 then 回调函数使用 catch 捕获了错误,那么这个错误就不会被传播出去,否则这个错误将会被传播出去,并且会触发全局错误处理函数。
async 函数
对于 async 函数,有一个额外的机制来处理错误,async 可以使用 try-catch 来捕获错误,对于同步代码可以正常捕获,对于 Promise 可以使用 await 使其加入当前的同步代码层级,让当前代码层级的 try-catch 来捕获。
异步生成器函数
框架提供的回调 API 函数
- Vue 的生命周期钩子函数等
- React 的 hook 函数等
- Express 中间件