本篇內(nèi)容介紹了“javascript Async函數(shù)相關(guān)知識點有哪些”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的長壽網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
在異步處理方案中,目前最為簡潔優(yōu)雅的便是async函數(shù)(以下簡稱A函數(shù))。經(jīng)過必要的分塊包裝后,A函數(shù)能使多個相關(guān)的異步操作如同同步操作一樣聚合起來,使其相互間的關(guān)系更為清晰、過程更為簡潔、調(diào)試更為方便。它本質(zhì)是Generator函數(shù)的語法糖,通俗的說法是使用G函數(shù)進行異步處理的增強版。
學(xué)習(xí)A函數(shù)必須有Promise基礎(chǔ),***還了解Generator函數(shù),有需要的可查看延伸小節(jié)。
為了直觀的感受A函數(shù)的魅力,下面使用Promise和A函數(shù)進行了相同的異步操作。該異步的目的是獲取用戶的留言列表,需要分頁,分頁由后臺控制。具體的操作是:先獲取到留言的總條數(shù),再更正當(dāng)前需要顯示的頁數(shù)(每次切換到不同頁時,總數(shù)目可能會發(fā)生變化),***傳遞參數(shù)并獲取到相應(yīng)的數(shù)據(jù)。
let totalNum = 0; // Total comments number. let curPage = 1; // Current page index. let pageSize = 10; // The number of comment displayed in one page. // 使用A函數(shù)的主代碼。 async function dealWithAsync() { totalNum = await getListCount(); console.log('Get count', totalNum); if (pageSize * (curPage - 1) > totalNum) { curPage = 1; } return getListData(); } // 使用Promise的主代碼。 function dealWithPromise() { return new Promise((resolve, reject) => { getListCount().then(res => { totalNum = res; console.log('Get count', res); if (pageSize * (curPage - 1) > totalNum) { curPage = 1; } return getListData() }).then(resolve).catch(reject); }); } // 開始執(zhí)行dealWithAsync函數(shù)。 // dealWithAsync().then(res => { // console.log('Get Data', res) // }).catch(err => { // console.log(err); // }); // 開始執(zhí)行dealWithPromise函數(shù)。 // dealWithPromise().then(res => { // console.log('Get Data', res) // }).catch(err => { // console.log(err); // }); function getListCount() { return createPromise(100).catch(() => { throw 'Get list count error'; }); } function getListData() { return createPromise([], { curPage: curPage, pageSize: pageSize, }).catch(() => { throw 'Get list data error'; }); } function createPromise( data, // Reback data params = null, // Request params isSucceed = true, timeout = 1000, ) { return new Promise((resolve, reject) => { setTimeout(() => { isSucceed ? resolve(data) : reject(data); }, timeout); }); }
對比dealWithAsync和dealWithPromise兩個簡單的函數(shù),能直觀的發(fā)現(xiàn):使用A函數(shù),除了有await關(guān)鍵字外,與同步代碼無異。而使用Promise則需要根據(jù)規(guī)則增加很多包裹性的鏈?zhǔn)讲僮?,產(chǎn)生了太多回調(diào)函數(shù),不夠簡約。另外,這里分開了每個異步操作,并規(guī)定好各自成功或失敗時傳遞出來的數(shù)據(jù),近乎實際開發(fā)。
1.1 形式
A函數(shù)也是函數(shù),所以具有普通函數(shù)該有的性質(zhì)。不過形式上有兩點不同:一是定義A函數(shù)時,function關(guān)鍵字前需要有async關(guān)鍵字(意為異步),表示這是個A函數(shù)。二是在A函數(shù)內(nèi)部可以使用await關(guān)鍵字(意為等待),表示會將其后面跟隨的結(jié)果當(dāng)成異步操作并等待其完成。
以下是它的幾種定義方式。
// 聲明式 async function A() {} // 表達式 let A = async function () {}; // 作為對象屬性 let o = { A: async function () {} }; // 作為對象屬性的簡寫式 let o = { async A() {} }; // 箭頭函數(shù) let o = { A: async () => {} };
1.2 返回值
執(zhí)行A函數(shù),會固定的返回一個Promise對象。
得到該對象后便可監(jiān)設(shè)置成功或失敗時的回調(diào)函數(shù)進行監(jiān)聽。如果函數(shù)執(zhí)行順利并結(jié)束,返回的P對象的狀態(tài)會從等待轉(zhuǎn)變成成功,并輸出return命令的返回結(jié)果(沒有則為undefined)。如果函數(shù)執(zhí)行途中失敗,JS會認為A函數(shù)已經(jīng)完成執(zhí)行,返回的P對象的狀態(tài)會從等待轉(zhuǎn)變成失敗,并輸出錯誤信息。
// 成功執(zhí)行案例 A1().then(res => { console.log('執(zhí)行成功', res); // 10 }); async function A1() { let n = 1 * 10; return n; } // 失敗執(zhí)行案例 A2().catch(err => { console.log('執(zhí)行失敗', err); // i is not defined. }); async function A2() { let n = 1 * i; return n; }
1.3 await
只有在A函數(shù)內(nèi)部才可以使用await命令,存在于A函數(shù)內(nèi)部的普通函數(shù)也不行。
引擎會統(tǒng)一將await后面的跟隨值視為一個Promise,對于不是Promise對象的值會調(diào)用Promise.resolve()進行轉(zhuǎn)化。即便此值為一個Error實例,經(jīng)過轉(zhuǎn)化后,引擎依然視其為一個成功的Promise,其數(shù)據(jù)為Error的實例。
當(dāng)函數(shù)執(zhí)行到await命令時,會暫停執(zhí)行并等待其后的Promise結(jié)束。如果該P對象最終成功,則會返回成功的返回值,相當(dāng)將await xxx替換成返回值。如果該P對象最終失敗,且錯誤沒有被捕獲,引擎會直接停止執(zhí)行A函數(shù)并將其返回對象的狀態(tài)更改為失敗,輸出錯誤信息。
***,A函數(shù)中的return x表達式,相當(dāng)于return await x的簡寫。
// 成功執(zhí)行案例 A1().then(res => { console.log('執(zhí)行成功', res); // 約兩秒后輸出100。 }); async function A1() { let n1 = await 10; let n2 = await new Promise(resolve => { setTimeout(() => { resolve(10); }, 2000); }); return n1 * n2; } // 失敗執(zhí)行案例 A2().catch(err => { console.log('執(zhí)行失敗', err); // 約兩秒后輸出10。 }); async function A2() { let n1 = await 10; let n2 = await new Promise((resolve, reject) => { setTimeout(() => { reject(10); }, 2000); }); return n1 * n2; }
2.1 繼發(fā)與并發(fā)
對于存在于JS語句(for, while等)的await命令,引擎遇到時也會暫停執(zhí)行。這意味著可以直接使用循環(huán)語句處理多個異步。
以下是處理繼發(fā)的兩個例子。A函數(shù)處理相繼發(fā)生的異步尤為簡潔,整體上與同步代碼無異。
// 兩個方法A1和A2的行為結(jié)果相同,都是每隔一秒輸出10,輸出三次。 async function A1() { let n1 = await createPromise(); console.log('N1', n1); let n2 = await createPromise(); console.log('N2', n2); let n3 = await createPromise(); console.log('N3', n3); } async function A2() { for (let i = 0; i< 3; i++) { let n = await createPromise(); console.log('N' + (i + 1), n); } } function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(10); }, 1000); }); }
接下來是處理并發(fā)的三個例子。A1函數(shù)使用了Promise.all生成一個聚合異步,雖然簡單但靈活性降低了,只有都成功和失敗兩種情況。A3函數(shù)相對A2僅僅為了說明應(yīng)該怎樣配合數(shù)組的遍歷方法使用async函數(shù)。重點在A2函數(shù)的理解上。
A2函數(shù)使用了循環(huán)語句,實際是繼發(fā)的獲取到各個異步值,但在總體的時間上相當(dāng)并發(fā)(這里需要好好理解一番)。因為一開始創(chuàng)建reqs數(shù)組時,就已經(jīng)開始執(zhí)行了各個異步,之后雖然是逐一繼發(fā)獲取,但總花費時間與遍歷順序無關(guān),恒等于耗時最多的異步所花費的時間(不考慮遍歷、執(zhí)行等其它的時間消耗)。
// 三個方法A1, A2和A3的行為結(jié)果相同,都是在約一秒后輸出[10, 10, 10]。 async function A1() { let res = await Promise.all([createPromise(), createPromise(), createPromise()]); console.log('Data', res); } async function A2() { let res = []; let reqs = [createPromise(), createPromise(), createPromise()]; for (let i = 0; i< reqs.length; i++) { res[i] = await reqs[i]; } console.log('Data', res); } async function A3() { let res = []; let reqs = [9, 9, 9].map(async (item) => { let n = await createPromise(item); return n + 1; }); for (let i = 0; i< reqs.length; i++) { res[i] = await reqs[i]; } console.log('Data', res); } function createPromise(n = 10) { return new Promise(resolve => { setTimeout(() => { resolve(n); }, 1000); }); }
2.2 錯誤處理
一旦await后面的Promise轉(zhuǎn)變成rejected,整個async函數(shù)便會終止。然而很多時候我們不希望因為某個異步操作的失敗,就終止整個函數(shù),因此需要進行合理錯誤處理。注意,這里所說的錯誤不包括引擎解析或執(zhí)行的錯誤,僅僅是狀態(tài)變?yōu)閞ejected的Promise對象。
處理的方式有兩種:一是先行包裝Promise對象,使其始終返回一個成功的Promise。二是使用try.catch捕獲錯誤。
// A1和A2都執(zhí)行成,且返回值為10。 A1().then(console.log); A2().then(console.log); async function A1() { let n; n = await createPromise(true); return n; } async function A2() { let n; try { n = await createPromise(false); } catch (e) { n = e; } return n; } function createPromise(needCatch) { let p = new Promise((resolve, reject) => { reject(10); }); return needCatch ? p.catch(err => err) : p; }
2.3 實現(xiàn)原理
前言中已經(jīng)提及,A函數(shù)是使用G函數(shù)進行異步處理的增強版。既然如此,我們就從其改進的方面入手,來看看其基于G函數(shù)的實現(xiàn)原理。A函數(shù)相對G函數(shù)的改進體現(xiàn)在這幾個方面:更好的語義,內(nèi)置執(zhí)行器和返回值是Promise。
更好的語義。G函數(shù)通過在function后使用*來標(biāo)識此為G函數(shù),而A函數(shù)則是在function前加上async關(guān)鍵字。在G函數(shù)中可以使用yield命令暫停執(zhí)行和交出執(zhí)行權(quán),而A函數(shù)是使用await來等待異步返回結(jié)果。很明顯,async和await更為語義化。
// G函數(shù) function* request() { let n = yield createPromise(); } // A函數(shù) async function request() { let n = await createPromise(); } function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(10); }, 1000); }); }
內(nèi)置執(zhí)行器。調(diào)用A函數(shù)便會一步步自動執(zhí)行和等待異步操作,直到結(jié)束。如果需要使用G函數(shù)來自動執(zhí)行異步操作,需要為其創(chuàng)建一個自執(zhí)行器。通過自執(zhí)行器來自動化G函數(shù)的執(zhí)行,其行為與A函數(shù)基本相同。可以說,A函數(shù)相對G函數(shù)***改進便是內(nèi)置了自執(zhí)行器。
// 兩者都是每隔一秒鐘打印出10,重復(fù)兩次。 // A函數(shù) A(); async function A() { let n1 = await createPromise(); console.log(n1); let n2 = await createPromise(); console.log(n2); } // G函數(shù),使用自執(zhí)行器執(zhí)行。 spawn(G); function* G() { let n1 = yield createPromise(); console.log(n1); let n2 = yield createPromise(); console.log(n2); } function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); } function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(10); }, 1000); }); }
2.4 執(zhí)行順序
在了解A函數(shù)內(nèi)部與包含它外部間的執(zhí)行順序前,需要明白兩點:一為Promise的實例方法是推遲到本輪事件末尾才執(zhí)行的后執(zhí)行操作,詳情請查看鏈接。二為Generator函數(shù)是通過調(diào)用實例方法來切換執(zhí)行權(quán)進而控制程序執(zhí)行順序,詳情請查看鏈接。理解好A函數(shù)的執(zhí)行順序,能更加清楚的把握此三者的存在。
先看以下代碼,對比A1、A2和A3方法的結(jié)果。
F(A1); // 接連打印出:1 3 4 2 5。F(A2); // 接連打印出:1 3 2 4 5。F(A3); // 先打印出:1 3 2,隔兩秒后打印出:4 9。function F(A) { console.log(1); A().then(console.log); console.log(2); } async function A1() { console.log(3); console.log(4); return 5; } async function A2() { console.log(3); let n = await 5; console.log(4); return n; } async function A3() { console.log(3); let n = await createPromise(); console.log(4); return n; } function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(9); }, 2000); }); }
從結(jié)果上可歸納出一些表面形態(tài)。執(zhí)行A函數(shù),會即刻執(zhí)行其函數(shù)體,直到遇到await命令。遇到await命令后,執(zhí)行權(quán)會轉(zhuǎn)向A函數(shù)外部,即不管A函數(shù)內(nèi)部執(zhí)行而開始執(zhí)行外部代碼。執(zhí)行完外部代碼(本輪事件)后,才繼續(xù)執(zhí)行之前await命令后面的代碼。
歸納到此已成功一半,之后著手分析其成因。如果客官您對本樓有所了解,那一定不會忘記‘自執(zhí)行器’這位大嬸吧?估計是忘記了。A函數(shù)的本質(zhì)就是帶有自執(zhí)行器的G函數(shù),所以探究A函數(shù)的執(zhí)行原理就是探究使用自執(zhí)行器的G函數(shù)的執(zhí)行原理。想起了?
再看下面代碼,使用相同邏輯的G函數(shù)會得到與A函數(shù)相同的結(jié)果。
F(A); // 先打印出:1 3 2,隔兩秒后打印出:4 9。 F(() => { return spawn(G); }); // 先打印出:1 3 2,隔兩秒后打印出:4 9。 function F(A) { console.log(1); A().then(console.log); console.log(2); } async function A() { console.log(3); let n = await createPromise(); console.log(4); return n; } function* G() { console.log(3); let n = yield createPromise(); console.log(4); return n; } function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(9); }, 2000); }); } function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }
自動執(zhí)行G函數(shù)時,遇到y(tǒng)ield命令后會使用Promise.resolve包裹其后的表達式,并為其設(shè)置回調(diào)函數(shù)。無論該Promise是立刻有了結(jié)果還是過某段時間之后,其回調(diào)函數(shù)都會被推遲到在本輪事件末尾執(zhí)行。之后再是下一步,再下一步。同樣的道理適用于A函數(shù),當(dāng)遇到await命令時(此處略去三五字),所以有了如此這般的執(zhí)行順序。
“javascript Async函數(shù)相關(guān)知識點有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
網(wǎng)站標(biāo)題:javascriptAsync函數(shù)相關(guān)知識點有哪些
URL標(biāo)題:http://muchs.cn/article16/gddjdg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、企業(yè)建站、服務(wù)器托管、軟件開發(fā)、小程序開發(fā)、定制網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)