屬于,async是es6的新特性,用于表明程序里面可能有異步過程。用async關(guān)鍵字聲明的函數(shù)返回的是一個Promise對象,如果在函數(shù)中return一個直接量,async會把這個直接量通過Promise.resolve()封裝成Promise對象;當async函數(shù)沒有返回值時,返回“Promise.resolve(undefined)”。
前端(vue)入門到精通課程:進入學(xué)習(xí)
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API調(diào)試工具:點擊使用
本教程操作環(huán)境:windows7系統(tǒng)、ECMAScript 6版、Dell G3電腦。
ES6新特性 async和await關(guān)鍵字
1、初步了解
我們先從字面意思來理解這兩個關(guān)鍵字,async是asynchronous(異步)的簡寫,而await可以認為是async wait的簡寫。所以async可以理解為用于聲明一個函數(shù)是異步的,而await用于等待一個異步任務(wù)執(zhí)行完成。
async和await關(guān)鍵字讓我們可以用一種更簡潔的方式寫出基于promise的異步行為,而無需刻意地鏈式調(diào)用promise。
接下來我們通過先幾個例子,初步了解一下async和await的作用。
知識點1: 用 async 關(guān)鍵字聲明的函數(shù)返回的是一個 Promise 對象。如果在函數(shù)中 return
一個直接量,async 會把這個直接量通過 Promise.resolve()
封裝成 Promise 對象。當 async
函數(shù)沒有返回值時,返回 Promise.resolve(undefined)
//定義一個普通函數(shù),返回一個字符串 function test() { return "hello async"; } const result1 = test(); console.log(result1); //輸出一個字符串 hello async //定義一個使用了async修飾的函數(shù),同樣返回一個字符串 async function testAsync() { return "hello async"; } const result2 = testAsync(); console.log(result2); //輸出一個Promise對象 Promise {<fulfilled>: 'hello async'}
//async較好的用法 async function testAsync(){ //返回一個Promise對象 return new Promise((resolve, reject)=>{ //處理異步任務(wù) setTimeout(function () { resolve("testAsync") }, 1000); }) } //async通常用于聲明一個處理異步任務(wù)且返回了Promise對象的函數(shù)
知識點2: await關(guān)鍵字只能使用在被async聲明的函數(shù)內(nèi),用于修飾一個Promise對象,使得該Promise對象處理的異步任務(wù)在當前協(xié)程上按順序同步執(zhí)行。
//定義一個使用async修飾的函數(shù),處理異步任務(wù) async function testAsync(){ return new Promise((resolve, reject)=>{ setTimeout(function () { resolve("testAsync") }, 1000); }) }
//定義一個函數(shù),直接調(diào)用testAsync函數(shù) function testAwait(){ console.log('testAsync調(diào)用前') testAsync().then(res=>{ console.log(res) //輸出"testAsync" }) console.log('testAsync調(diào)用后') } /***** 輸出如下 *****/ testAsync調(diào)用前 testAsync調(diào)用后 testAsync //盡管代碼按順序?qū)?,但不按順序?zhí)行,因為testAsync()是異步函數(shù)
//定義一個函數(shù)(不使用async聲明該函數(shù))用await修飾調(diào)用testAsync函數(shù) function testAwait(){ console.log('testAsync調(diào)用前') await testAsync().then(res=>{ //使用await關(guān)鍵字修飾 console.log(res) }) console.log('testAsync調(diào)用后') } //調(diào)用testAwait()函數(shù) testAwait() //報錯:Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules,因為await只能使用在被async修飾的函數(shù)內(nèi)。
//定義一個函數(shù)(使用async聲明該函數(shù))用await修飾調(diào)用testAsync函數(shù) async function testAwait(){ console.log('testAsync調(diào)用前') await testAsync().then(res=>{ console.log(res) }) console.log('testAsync調(diào)用后') } /***** 輸出如下 *****/ testAsync調(diào)用前 testAsync testAsync調(diào)用后 //使用了await關(guān)鍵字修飾,使得代碼按照順序執(zhí)行,即同步執(zhí)行
2、async關(guān)鍵字
(1)用于表明程序里面可能有異步過程
(2)async函數(shù)返回值的類型為Promise對象: 這是和普通函數(shù)本質(zhì)上不同的地方,也是使用時重點注意的地方;
- return newPromise( ),這個用法符合async函數(shù)本意;
- return data,特別注意到是這樣子寫相當于Promise.resolve(data),返回的data被封裝成一個Promise對象,但是在調(diào)用async函數(shù)的地方通過簡單的=是拿不到這個返回值data的,因為返回值是一個Promise對象,所以需要用.then(data => { })方式才可以拿到這個data;
- 如果沒有返回值,相當于返回了Promise.resolve(undefined);
(3)無等待,非阻塞:使用async關(guān)鍵字聲明的函數(shù)里面如果有異步過程可能會等待,但是函數(shù)本身會馬上返回,不會阻塞當前主線程。如果在函數(shù)里面使用了await關(guān)鍵字修飾的異步過程,其工作在相應(yīng)的協(xié)程上,會阻塞等待異步任務(wù)的完成再返回。
//定義一個函數(shù),處理異步任務(wù)(使用定時器模擬),返回一個Promise對象 async function testAsync(){ return new Promise((resolve, reject) => { setTimeout(function () { resolve("成功調(diào)用testAsync") }, 1000); }); } //定義一個函數(shù),使用await關(guān)鍵字修飾調(diào)用testAsync()函數(shù) async function testAwait(){ //使用了await關(guān)鍵字修飾調(diào)用testAsyn()函數(shù) await this.testAsync().then(res=>{ console.log(res) //輸出的是testAsync()函數(shù)resolve的值 }); console.log("helloAsync"); } //主線程 console.log('testAwait調(diào)用前') testAwait(); console.log('testAwait調(diào)用后') /***** 輸出結(jié)果如下 *****/ testAwait調(diào)用前 testAwait調(diào)用后 //因為testAwait()函數(shù)使用了async關(guān)鍵字修飾,所以不會阻塞主線程的執(zhí)行,所以這兩句話會先直接輸出,然后再執(zhí)行testAwait()函數(shù) 成功調(diào)用testAsync //因為testAwait()函數(shù)在內(nèi)部調(diào)用testAsync()函數(shù)時使用了await關(guān)鍵字修飾,所以在對應(yīng)的協(xié)程上會阻塞,等待testAsync()函數(shù)執(zhí)行完,再輸出下面那句'helloAsync' helloAsync
3、await關(guān)鍵字
(1)await只能在async函數(shù)內(nèi)部使用:不能放在普通函數(shù)里面,否則會報錯。
(2)await關(guān)鍵字后面跟的是一個Promise對象。如果跟的是一個函數(shù),則這個函數(shù)應(yīng)當返回一個Promise對象。如果跟的是非Promise對象,則會通過Promise.resolve( )函數(shù)自動將這個東西包裝成一個Promise對象并置于fulfilled狀態(tài)。
//例如: const a = await 'Hello Await' // 相當于 const a = await Promise.resolve('Hello Await'); console.log(a) //輸出 'Hello Await'
(3)await的本質(zhì)是等待它所修飾的Promise對象的fulfilled狀態(tài),并把resolve(data)的數(shù)據(jù)data返回。
意思是,如果await后面跟的是一個 Promise
對象,await
就會阻塞后面的代碼,等著 Promise
對象 resolve
,然后得到 resolve
的值,作為 await
表達式的運算結(jié)果。
async function testAsync(){ return new Promise((resolve, reject) => { setTimeout(function () { resolve("成功調(diào)用testAsync") }, 1000); }); } const a = await testAsync() //這里的a就會拿到testAsync函數(shù)resolve的數(shù)據(jù) console.log(a) //在一秒后輸出'成功調(diào)用testAsync'
(4)await并不關(guān)心它所修飾的Promise對象的rejected狀態(tài),即reject(data)的數(shù)據(jù)data并不會被await處理,所以建議通過Promise對象調(diào)用catch去捕獲。
async testAwait(){ //變量a用于接收testAsync()函數(shù)resolve的數(shù)據(jù) let a = await testAsync().catch(err=>{ //處理異常和reject的數(shù)據(jù) }) }
4、深入講解async和await
(1)執(zhí)行順序
//定義一個函數(shù),該函數(shù)接收一個參數(shù),1s后再返回參數(shù)的兩倍 async function double(num) { return new Promise((resolve, reject) => { setTimeout(() => { //使用定時器模擬異步任務(wù) resolve(2 * num) //將運算結(jié)果交給resolve }, 1000); }) } async function getResult () { console.log('double調(diào)用前') //順序:2 let result = await double(10); //將10作為參數(shù)傳遞給double函數(shù) //result變量用于接收double()函數(shù)resolve的值 console.log(result); //順序:4 console.log('double調(diào)用后') //順序:4 } console.log('getResult調(diào)用前') //順序:1 getResult(); console.log('getResult調(diào)用后') //順序:3 /***** 依次輸出如下 *****/ getResult調(diào)用前 double調(diào)用前 getResult調(diào)用后 20 //1s后輸出 double調(diào)用后
①首先打印輸出getResult調(diào)用前
,同步代碼,順序執(zhí)行;
②然后調(diào)用方法getResult( ),打印輸出double調(diào)用前
,同步代碼,順序執(zhí)行;
③再調(diào)用異步方法double( )
如果此處沒有使用await關(guān)鍵字修飾,則依次輸出的是:getResult調(diào)用前、double調(diào)用前、double調(diào)用后、getResult調(diào)用后、1s后輸出20
因為異步操作不會影響其他代碼的執(zhí)行,所以會將其他代碼按順序執(zhí)行完,最后再執(zhí)行double函數(shù)
因為這里使用了await關(guān)鍵字,所以getResult( )的代碼執(zhí)行到這里就會被阻塞,等到double函數(shù)resolve了,再往下執(zhí)行
④盡管getResult函數(shù)內(nèi)部被await阻塞了,由于getResult函數(shù)本身也是個async函數(shù),所以它不會影響getResult函數(shù)外面的代碼執(zhí)行。因為調(diào)用async函數(shù)不會造成阻塞,它內(nèi)部的所有阻塞都被封裝在一個Promise對象中異步執(zhí)行。
⑤所以在調(diào)用getResult函數(shù)后,會繼續(xù)向下執(zhí)行,即打印輸出getResult調(diào)用后
⑥當1s之后,異步函數(shù)double執(zhí)行完成,將結(jié)果交給resolve。
⑦通過await關(guān)鍵字接收到double函數(shù)resolve的值,賦值給result變量。打印輸出20
⑧因為使用了await阻塞將異步變?yōu)橥剑栽诖蛴≥敵?0后再打印輸出double調(diào)用后
(2)處理reject回調(diào)
//方法一:通過promise對象的catch進行捕獲 function a(){ return new Promise((resolve,reject) => { setTimeout(() => { reject("something") }, 1000) }) } async function b(){ let r = await a().catch((err)=>{ console.log(err) }) }
//方法二:通過try/catch語句處理 function a(){ return new Promise((resolve,reject) => { setTimeout(() => { reject("something") }, 1000) }) } async function b(){ let r = null try{ r = await a() }catch(err){ console.log(err) } }
(3)使用await優(yōu)化Promise對象的回調(diào)地獄問題
在Promise章節(jié)中我們通過了Promise對象的then( )方法鏈式調(diào)用解決了回調(diào)地獄問題,但看起來仍然不夠美觀,我們可以通過await優(yōu)化一下,讓它看起來更符合我們平時代碼的編寫習(xí)慣。
//原本的解決方案 //第二個請求依賴于第一個請求的返回值,第三個請求依賴于第二個請求的返回值 request1().then(function(data){ return request2(data) }).then(function(data){ return request3(data) }) //這里只發(fā)送了三次請求,代碼看起來還不錯,雖然它已經(jīng)比普通的回調(diào)函數(shù)形式好了很多。 //那如果需要發(fā)送五次或十次請求呢?代碼也許會沒那么美觀,接下來我們使用學(xué)習(xí)到的await去解決這個問題。
原本的要求是每個請求都依賴于上一個請求的返回值,那么是不是得等一個請求完,才能發(fā)送下一個請求?這時我們可以思考一下,await的作用是什么?是不是對一個Promise對象去進行阻塞,使其狀態(tài)變?yōu)閒ulfilled后獲取resolve的值。這不就正是我們所需要的。
//使用await的解決方案 var res1 = await request1() //將request1的返回值賦值給res1 var res2 = await request2(res1) //將res1作為參數(shù)傳給request2,并將request2的返回值賦值給res2 var res3 = await request3(res2) //同理 //這樣子寫的代碼更加的美觀,并且更符合我們平時編寫代碼的習(xí)慣
【