async/await
其实就是Generator的语法糖。 如果对Generator还不太熟悉可以先看这篇 听说你还没听过 ES6 的 Generator 函数?
async
函数其实就相当于funciton *
的作用
await
就相当与yield
的作用。
而在async/await
机制中,自动包含了上述封装出来的spawn
自动执行函数 。
async
函数是ES6的新语法;使得异步操作变得更加方便。 使用关键字async
来修饰,表示函数里面可能有异步操作,在函数内部使用await
来表示异步。
async
函数中如果没有await
,那么和普通函数一样的
一旦加了 await
;那么 await
下面的代码就是异步的 ;
微任务和宏任务
微任务和宏任务: 这两个都是异步队列中的任务。
异步任务: setTimeout
、setInterval
、事件、promise
的then
、ajax
、async
await
微任务: promise
的 then
、 async
****await
、 process.nextTick
宏任务 : setTimeout
、 setInterval
、 ajax
;
执行顺序: 先执行同步任务,再执行异步任务;先执行微任务,再执行宏任务;
async
函数基本使用
async函数是 Generator 函数的语法糖。async声明该函数是异步的,且该函数会返回一个promise。
语法规则:
async
是 function
的一个前缀,只有 async
函数中才能使用 await
语法
async
函数是一个 Promise
对象,有无 resolve
取决于有无在函数中 return
值
await
后面跟的是一个 Promise
对象,如果不是,则会包裹一层 Promise.resolve()
1 2 3 4 5 6 7 8 9 10 11 12 function fn1 ( ) { console .log (200 ); } async function fn ( ) { await fn1 (); console .log (100 ); } console .log (fn ()); fn ().then (function (a ){ console .log (a); })
async 返回一个promise的实例; 默认是成功态 ;async函数内部 return语句返回的值 ,会成为 then方法回调函数的参数 。
1 2 3 4 5 6 7 8 async function fn ( ) { return 1 ; } console .log (fn ());fn ().then (function (a ) { console .log (a); });
下面这个例子中:fn1
函数返回的是一个promise,fn
函数中,只有当 await
后面的执行成功,也就是 promise
的状态变成 Fulfilled
( resolve(参数)
)了之后才会把 await
后面的代码放入微任务队列里面 ,整个 async
函数结束,继续执行后面的代码。 而且 await
的返回值就是 resolve(参数)
中的参数 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function fn1 ( ) { return new Promise (function (resolve,reject ) { setTimeout (function ( ) { console .log (300 ); resolve () },200 ) }) } async function fn ( ) { await fn1 (); console .log (200 ); } fn ().then (function ( ) {}).then (function ( ) { })
async
函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变 也就是说,只有当 async 函数内部的异步操作都执行完,才会执行 then 方法的回调 。
async
函数的多种形式
函数声明 :async function fn(){}
函数表达式 :let fn = async function(){}
箭头函数 :let fn = async () => {}
对象的方法 :let obj = { async fn(){} }; obj.fn().then(...)
class的方法 :
1 2 3 4 5 6 7 8 9 10 11 class Storage { constructor ( ) { this .cachePromise = caches.open ('avatars' ); } async getAvatar (name ) { const cache = await this .cachePromise ; return cache.match (`/avatars/${name} .jpg` ); } } const storage = new Storage ();storage.getAvatar ('jake' ).then (…);
async
函数的错误处理
如果 async
函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject
状态。抛出的错误而会被 catch
方法回调函数接收到。
如果await
后面的异步操作出错,那么等同于async
函数返回的 Promise 对象被reject
。 防止出错的方法,也是将其放在 try...catch
代码块之中 。
await
的多种类型await
+Promise
这是最常见的场景。
await
会等待Promise
的状态改为fullfilled
:
如果成功,那么会 将 async
函数剩余任务(也就是 await
后面的代码)推入到微任务队列
如果失败,那么剩余任务不会被推入微任务队列执行,它会 返回 Promise.reject(err)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 async function async1 ( ) { console .log ('async1 start' ) await async2 () console .log ('async1 end' ) } async function async2 ( ) { console .log ('async2 start' ) return new Promise ((resolve, reject ) => { resolve () console .log ('async2 promise' ) }) } console .log ('script start' )setTimeout (function ( ) { console .log ('setTimeout' ) }, 0 ) async1 ()new Promise (function (resolve ) { console .log ('promise1' ) resolve () }) .then (function ( ) { console .log ('promise2' ) }) .then (function ( ) { console .log ('promise3' ) }) console .log ('script end' )
await
+普通值
即使await
右边非函数,只是一个普通的数值,但它本质上是将其转化为 Promise.resolve(普通值)
,所以会返回一个成功的 promise
。
因此 , 当await等待到了成功的结果后,它会将async函数剩余内容(也就是 await
后面的代码)推入到微任务队列中等待执行 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async function run ( ) { console .log ('start 1' ) const res = await 2 console .log (res) console .log ('end' ) } run ()console .log ('3' )
await+函数
如果await
右边是一个函数,它会立刻执行这个函数,而且只有当这个函数执行结束后(即函数完成)!才会将 async
剩余任务(也就是 await
后面的代码)推入微任务队列 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function fn ( ) { console .log ('fn start' ) console .log ('fn end' ) } async function run ( ) { console .log ('start 1' ) const res = await fn () console .log (res) console .log ('end' ) } run ()console .log ('3' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 async function async1 ( ) { console .log (1 ) await async2 () console .log (2 ) } const async2 = async ( ) => { await (async () => { await (() => { console .log (3 ) })() console .log (4 ) })() } const async3 = async ( ) => { Promise .resolve ().then (() => { console .log (6 ) }) } async1 ()console .log (7 )async3 ()
如果await后面的返回的promise状态变成 rejected
,那么它 将不会再把剩余任务推入到微任务队列,跳过整个 async
函数继续执行后面的代码,并在执行完之后 Uncaught
:
await
+定时器(函数)
定时器setTimeOut()
和setInterval()
也都是函数,不过与普通函数不同的是,定时器函数返回的是一个 定时器ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 async function async1 ( ) { console .log (1 ) await async2 () console .log (2 ) } const async2 = async ( ) => { await setTimeout ((_ ) => { Promise .resolve ().then ((_ ) => { console .log (3 ) }) console .log (4 ) }, 0 ) } const async3 = async ( ) => { Promise .resolve ().then (() => { console .log (6 ) }) } async1 ()console .log (7 )async3 ()
为什么要使用async/await? 都已经有Promise了,为什么还要使用async/await
?
可以隐藏 Promise ,更易于理解 假设我们想请求一个接口,然后把响应的数据打印出来,并且捕获异常。
1 2 3 4 5 6 7 8 9 function logFetch (url ) { return fetch (url) .then (response => response.text ()) .then (text => { console .log (text); }).catch (err => { console .error ('fetch failed' , err); }); }
1 2 3 4 5 6 7 8 9 async function logFetch (url ) { try { const response = await fetch (url); console .log (await response.text ()); } catch (err) { console .log ('fetch failed' , err); } }
虽然代码的行数差不多,但是代码看起来更加简洁,少了很多 then
的嵌套 。请求一个接口数据,然后打印,就像你看到的,很简单。
用同步的思路写异步逻辑 想获取一个网络资源的大小:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function getResponseSize (url ) { return fetch (url).then (response => { const reader = response.body .getReader (); let total = 0 ; return reader.read ().then (function processResult (result ) { if (result.done ) return total; const value = result.value ; total += value.length ; console .log ('Received chunk' , value); return reader.read ().then (processResult); }) }); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const processResult = (result ) =>{ if (result.done ) return total; const value = result.value ; total += value.length ; console .log ('Received chunk' , value); return reader.read ().then (processResult); } function getResponseSize (url ) { return fetch (url).then (response => { const reader = response.body .getReader (); let total = 0 ; return reader.read ().then (processResult) }); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 async function getResponseSize (url ) { const response = await fetch (url); const reader = response.body .getReader (); let result = await reader.read (); let total = 0 ; while (!result.done ) { const value = result.value ; total += value.length ; console .log ('Received chunk' , value); result = await reader.read (); } return total; }
因为 await 表达式会阻塞运行,甚至可以直接阻塞循环,所以整体看起来像同步的代码,也更符合直觉,更容易读懂这个代码。
解决了Promise参数传递麻烦的弊端 假设一个业务,分多个步骤完成,每个步骤都是异步的而且依赖于上一个步骤的结果,并且每一个步骤都需要之前每个步骤的结果。。用 setTimeout
来模拟异步操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function takeLongTime (n ){ return new Promise ((resolve ) => { setTimeout (() => resolve (n + 200 ),n); }) } function step1 (n ){ console .log (`step1 with ${n} ` ); return takeLongTime (n); } function step2 (m,n ){ console .log (`step2 with ${m} + ${n} ` ); return takeLongTime (m + n); } function step3 (k,m,n ){ console .log (`step3 with ${k} + ${m} + ${n} ` ); return takeLongTime (k + m + n); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function doIt ( ) { console .time ('doIt' ); let time1 = 300 ; step1 (time1) .then ((time2 ) => { return step2 (time1,time2) .then ((time3 ) => [time1,time2,time3]) }) .then ((times ) => { let [time1,time2,time3] = times; return step3 (time1,time2,time3) }) .then ((result ) => { console .log (`result is ${result} ` ); console .timeEnd ('doIt' ); }) } doIt ();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 async function doIt ( ) { console .time ('doIt' ); let time1 = 300 ; let time2 = await step1 (time1); let time3 = await step2 (time2,time1); let result = await step3 (time3,time2,time1); console .log (`result is ${result} ` ); console .timeEnd ('doIt' ); } doIt ();
一堆参数处理,就是 Promise
方案的死穴—— 参数传递太麻烦了,而async/await
则解决了Promise
参数传递麻烦的弊端
async/await的错误捕获
await
命令后面的 Promise 对象,运行结果可能是 rejected
,所以最好 把 await
命令放在 try...catch
代码块中,这样就不会影响后面代码的运行 。
因为如果await后面的返回的promise
状态变成rejected
,那么它将不会再把剩余任务推入到微任务队列,剩余的代码也就不会再执行了。(await
不能提取reject
的结果)
1 2 3 4 5 6 7 8 9 const fn = async ( )=> { console .log ('我在await Promise之前' ); const result = await Promise .reject ('我是错误信息' ); console .log (result); console .log ('我在await Promise之后' ); } fn ()console .log ('我在fn()之后' )
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const fn = async ( )=> { console .log ('我在await Promise之前' ); try { const result = await Promise .reject ('我是错误信息' ); console .log (result); }catch (e){ console .log ('error:' , e) }finally { console .log ('我在finally里面,始终会执行' ); } console .log ('我在await Promise之后,我没有受到影响' ); } fn ()console .log ('我在fn()之后' )
运行结果:
可以看到虽然reject
了,但仍console.log('我在await Promise之后,我没有受到影响');
,说明不影响后面的代码执行。
小心await阻塞
由于 await
能够阻塞 async
函数的运行,所以代码看起来更像同步的代码,更容易阅读和理解。但是要小心 await
阻塞,因为有些阻塞是不必要的,不恰当使用可能会影响代码的性能。
假如要把一个网络数据和本地数据合并,错误的实例可能是这样子:
1 2 3 4 5 async function combineData (url, file ) { let networkData = await fetch (url) let fileData = await readeFile (file) console .log (networkData + fileData) }
其实我们不用等一个文件读完了,再去读下个文件,我们可以两个文件一起读,读完之后再进行合并,这样能提高代码的运行速度。我们可以这样写:
1 2 3 4 5 6 7 async function combineData (url, file ) { let fetchPromise = fetch (url) let readFilePromise = readFile (file) let networkData = await fetchPromise let fileData = await readFilePromise console .log (networkData + fileData) }
这样的话,就可以同时 网络请求 和 读取文件 了,可以节省很多时间。这里主要是利用了 Promise 一旦创建就立刻执行的特点——fetchPromise
和 readFilePromise
是两个异步操作的 Promise 对象,它们被创建后立即开始执行(一起执行的),而不是顺序执行 。
可以直接使用 Promise.all
的方式来处理,或者 await
后面跟 Promise.all
1 2 3 4 5 async function combineData (url, file ) { let promises = [fetch (url), readFile (file)] let [networkData, fileData] = await Promise .all (promises) console .log (networkData + fileData) }
async/await的实现原理 下面的代码实现了async
/await
的部分功能,yield
相当于await
,但是我们发现代码中存在了多次的嵌套调用,这还取决于 yield
的数量,这明显是不能容忍的,与此同时,gen
最终返回的也不是一个 Promise
对象,因此我们可以通过一个高阶函数来解决问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function p (num ) { return Promise .resolve (num * 2 ) } function * generator ( ) { const value1 = yield p (1 ) const value2 = yield p (value1) return value2 } const gen = generator ();const next1 = gen.next ()next1.value .then ((res1 ) => { console .log (res1) const next2 = gen.next (res1) next2.value .then ((res2 ) => { console .log (res2) }) })
所谓高阶函数,就是在函数中返回函数。
因为async
函数是一个可以返回Promise的函数,所以可以在高阶函数中返回一个返回值为 Promise
对象的函数:(同时处理一下嵌套调用问题=>改成递归调用 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 function p (num ) { return Promise .resolve (num * 2 ) } function * generator ( ) { const value1 = yield p (1 ) const value2 = yield p (value1) return value2 } function higherOrderFn (generatorFn ) { return () => { return new Promise ((resolve, reject ) => { let gen = generatorFn () const doYield = (val )=>{ console .log (val) let res try { res = gen.next (val) }catch (err){ reject (err) } const {value,done} = res if (done){ return resolve (value) }else { value.then ((val )=> {doYield (val)}) } } doYield () }) } } const asyncFn = higherOrderFn (generator)()
至此,generator
的函数体已经能和 async
函数实现契合了。