日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Async:簡潔優(yōu)雅的異步之道

前言

在異步處理方案中,目前最為簡潔優(yōu)雅的便是async函數(shù)(以下簡稱A函數(shù))。經(jīng)過必要的分塊包裝后,A函數(shù)能使多個(gè)相關(guān)的異步操作如同同步操作一樣聚合起來,使其相互間的關(guān)系更為清晰、過程更為簡潔、調(diào)試更為方便。它本質(zhì)是Generator函數(shù)的語法糖,通俗的說法是使用G函數(shù)進(jìn)行異步處理的增強(qiáng)版。

龍亭ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!

嘗試

學(xué)習(xí)A函數(shù)必須有Promise基礎(chǔ),***還了解Generator函數(shù),有需要的可查看延伸小節(jié)。

為了直觀的感受A函數(shù)的魅力,下面使用Promise和A函數(shù)進(jìn)行了相同的異步操作。該異步的目的是獲取用戶的留言列表,需要分頁,分頁由后臺控制。具體的操作是:先獲取到留言的總條數(shù),再更正當(dāng)前需要顯示的頁數(shù)(每次切換到不同頁時(shí),總數(shù)目可能會發(fā)生變化),***傳遞參數(shù)并獲取到相應(yīng)的數(shù)據(jù)。

 
 
 
  1. let totalNum = 0; // Total comments number. 
  2. let curPage = 1; // Current page index. 
  3. let pageSize = 10; // The number of comment displayed in one page. 
  4. // 使用A函數(shù)的主代碼。 
  5. async function dealWithAsync() { 
  6. totalNum = await getListCount(); 
  7. console.log('Get count', totalNum);
  8. if (pageSize * (curPage - 1) > totalNum) { 
  9. curPage = 1; 
  10. return getListData(); 
  11. // 使用Promise的主代碼。 
  12. function dealWithPromise() { 
  13. return new Promise((resolve, reject) => { 
  14. getListCount().then(res => { 
  15. totalNum = res; 
  16. console.log('Get count', res); 
  17. if (pageSize * (curPage - 1) > totalNum) { 
  18. curPage = 1; 
  19. return getListData() 
  20. }).then(resolve).catch(reject); 
  21. }); 
  22. // 開始執(zhí)行dealWithAsync函數(shù)。 
  23. // dealWithAsync().then(res => { 
  24. // console.log('Get Data', res) 
  25. // }).catch(err => { 
  26. // console.log(err); 
  27. // }); 
  28. // 開始執(zhí)行dealWithPromise函數(shù)。 
  29. // dealWithPromise().then(res => { 
  30. // console.log('Get Data', res) 
  31. // }).catch(err => { 
  32. // console.log(err); 
  33. // }); 
  34. function getListCount() {
  35. return createPromise(100).catch(() => { 
  36. throw 'Get list count error'; 
  37. }); 
  38. function getListData() { 
  39. return createPromise([], { 
  40. curPage: curPage, 
  41. pageSize: pageSize, 
  42. }).catch(() => { 
  43. throw 'Get list data error'; 
  44. }); 
  45. function createPromise( 
  46. data, // Reback data 
  47. params = null, // Request params 
  48. isSucceed = true, 
  49. timeout = 1000, 
  50. ) { 
  51. return new Promise((resolve, reject) => { 
  52. setTimeout(() => { 
  53. isSucceed ? resolve(data) : reject(data); 
  54. }, timeout); 
  55. }); 

對比dealWithAsync和dealWithPromise兩個(gè)簡單的函數(shù),能直觀的發(fā)現(xiàn):使用A函數(shù),除了有await關(guān)鍵字外,與同步代碼無異。而使用Promise則需要根據(jù)規(guī)則增加很多包裹性的鏈?zhǔn)讲僮鳎a(chǎn)生了太多回調(diào)函數(shù),不夠簡約。另外,這里分開了每個(gè)異步操作,并規(guī)定好各自成功或失敗時(shí)傳遞出來的數(shù)據(jù),近乎實(shí)際開發(fā)。

1 登堂

1.1 形式

A函數(shù)也是函數(shù),所以具有普通函數(shù)該有的性質(zhì)。不過形式上有兩點(diǎn)不同:一是定義A函數(shù)時(shí),function關(guān)鍵字前需要有async關(guān)鍵字(意為異步),表示這是個(gè)A函數(shù)。二是在A函數(shù)內(nèi)部可以使用await關(guān)鍵字(意為等待),表示會將其后面跟隨的結(jié)果當(dāng)成異步操作并等待其完成。

以下是它的幾種定義方式。

 
 
 
  1. // 聲明式 
  2. async function A() {} 
  3. // 表達(dá)式 
  4. let A = async function () {}; 
  5. // 作為對象屬性 
  6. let o = { 
  7. A: async function () {} 
  8. }; 
  9. // 作為對象屬性的簡寫式 
  10. let o = { 
  11. async A() {} 
  12. }; 
  13. // 箭頭函數(shù) 
  14. let o = { 
  15. A: async () => {} 
  16. }; 

1.2 返回值

執(zhí)行A函數(shù),會固定的返回一個(gè)Promise對象。

得到該對象后便可監(jiān)設(shè)置成功或失敗時(shí)的回調(diào)函數(shù)進(jìn)行監(jiān)聽。如果函數(shù)執(zhí)行順利并結(jié)束,返回的P對象的狀態(tài)會從等待轉(zhuǎn)變成成功,并輸出return命令的返回結(jié)果(沒有則為undefined)。如果函數(shù)執(zhí)行途中失敗,JS會認(rèn)為A函數(shù)已經(jīng)完成執(zhí)行,返回的P對象的狀態(tài)會從等待轉(zhuǎn)變成失敗,并輸出錯誤信息。

 
 
 
  1. // 成功執(zhí)行案例 
  2. A1().then(res => { 
  3. console.log('執(zhí)行成功', res); // 10 
  4. }); 
  5. async function A1() { 
  6. let n = 1 * 10; 
  7. return n; 
  8. // 失敗執(zhí)行案例 
  9. A2().catch(err => { 
  10. console.log('執(zhí)行失敗', err); // i is not defined. 
  11. }); 
  12. async function A2() { 
  13. let n = 1 * i; 
  14. return n; 

1.3 await

只有在A函數(shù)內(nèi)部才可以使用await命令,存在于A函數(shù)內(nèi)部的普通函數(shù)也不行。

引擎會統(tǒng)一將await后面的跟隨值視為一個(gè)Promise,對于不是Promise對象的值會調(diào)用Promise.resolve()進(jìn)行轉(zhuǎn)化。即便此值為一個(gè)Error實(shí)例,經(jīng)過轉(zhuǎn)化后,引擎依然視其為一個(gè)成功的Promise,其數(shù)據(jù)為Error的實(shí)例。

當(dāng)函數(shù)執(zhí)行到await命令時(shí),會暫停執(zhí)行并等待其后的Promise結(jié)束。如果該P(yáng)對象最終成功,則會返回成功的返回值,相當(dāng)將await xxx替換成返回值。如果該P(yáng)對象最終失敗,且錯誤沒有被捕獲,引擎會直接停止執(zhí)行A函數(shù)并將其返回對象的狀態(tài)更改為失敗,輸出錯誤信息。

***,A函數(shù)中的return x表達(dá)式,相當(dāng)于return await x的簡寫。

 
 
 
  1. // 成功執(zhí)行案例 
  2. A1().then(res => { 
  3. console.log('執(zhí)行成功', res); // 約兩秒后輸出100。 
  4. }); 
  5. async function A1() { 
  6. let n1 = await 10; 
  7. let n2 = await new Promise(resolve => { 
  8. setTimeout(() => { 
  9. resolve(10); 
  10. }, 2000); 
  11. }); 
  12. return n1 * n2; 
  13. // 失敗執(zhí)行案例 
  14. A2().catch(err => { 
  15. console.log('執(zhí)行失敗', err); // 約兩秒后輸出10。 
  16. }); 
  17. async function A2() { 
  18. let n1 = await 10; 
  19. let n2 = await new Promise((resolve, reject) => { 
  20. setTimeout(() => { 
  21. reject(10); 
  22. }, 2000); 
  23. }); 
  24. return n1 * n2;

2 入室

2.1 繼發(fā)與并發(fā)

對于存在于JS語句(for, while等)的await命令,引擎遇到時(shí)也會暫停執(zhí)行。這意味著可以直接使用循環(huán)語句處理多個(gè)異步。

以下是處理繼發(fā)的兩個(gè)例子。A函數(shù)處理相繼發(fā)生的異步尤為簡潔,整體上與同步代碼無異。

 
 
 
  1. // 兩個(gè)方法A1和A2的行為結(jié)果相同,都是每隔一秒輸出10,輸出三次。 
  2. async function A1() { 
  3. let n1 = await createPromise(); 
  4. console.log('N1', n1); 
  5. let n2 = await createPromise(); 
  6. console.log('N2', n2); 
  7. let n3 = await createPromise(); 
  8. console.log('N3', n3); 
  9. async function A2() { 
  10. for (let i = 0; i< 3; i++) { 
  11. let n = await createPromise(); 
  12. console.log('N' + (i + 1), n); 
  13. function createPromise() { 
  14. return new Promise(resolve => { 
  15. setTimeout(() => { 
  16. resolve(10); 
  17. }, 1000); 
  18. }); 

接下來是處理并發(fā)的三個(gè)例子。A1函數(shù)使用了Promise.all生成一個(gè)聚合異步,雖然簡單但靈活性降低了,只有都成功和失敗兩種情況。A3函數(shù)相對A2僅僅為了說明應(yīng)該怎樣配合數(shù)組的遍歷方法使用async函數(shù)。重點(diǎn)在A2函數(shù)的理解上。

A2函數(shù)使用了循環(huán)語句,實(shí)際是繼發(fā)的獲取到各個(gè)異步值,但在總體的時(shí)間上相當(dāng)并發(fā)(這里需要好好理解一番)。因?yàn)橐婚_始創(chuàng)建reqs數(shù)組時(shí),就已經(jīng)開始執(zhí)行了各個(gè)異步,之后雖然是逐一繼發(fā)獲取,但總花費(fèi)時(shí)間與遍歷順序無關(guān),恒等于耗時(shí)最多的異步所花費(fèi)的時(shí)間(不考慮遍歷、執(zhí)行等其它的時(shí)間消耗)。

 
 
 
  1. // 三個(gè)方法A1, A2和A3的行為結(jié)果相同,都是在約一秒后輸出[10, 10, 10]。 
  2. async function A1() {
  3. let res = await Promise.all([createPromise(), createPromise(), createPromise()]); 
  4. console.log('Data', res); 
  5. async function A2() { 
  6. let res = []; 
  7. let reqs = [createPromise(), createPromise(), createPromise()]; 
  8. for (let i = 0; i< reqs.length; i++) { 
  9. res[i] = await reqs[i]; 
  10. console.log('Data', res); 
  11. async function A3() { 
  12. let res = []; 
  13. let reqs = [9, 9, 9].map(async (item) => { 
  14. let n = await createPromise(item); 
  15. return n + 1; 
  16. }); 
  17. for (let i = 0; i< reqs.length; i++) { 
  18. res[i] = await reqs[i]; 
  19. console.log('Data', res); 
  20. function createPromise(n = 10) { 
  21. return new Promise(resolve => { 
  22. setTimeout(() => { 
  23. resolve(n); 
  24. }, 1000); 
  25. }); 

2.2 錯誤處理

一旦await后面的Promise轉(zhuǎn)變成rejected,整個(gè)async函數(shù)便會終止。然而很多時(shí)候我們不希望因?yàn)槟硞€(gè)異步操作的失敗,就終止整個(gè)函數(shù),因此需要進(jìn)行合理錯誤處理。注意,這里所說的錯誤不包括引擎解析或執(zhí)行的錯誤,僅僅是狀態(tài)變?yōu)閞ejected的Promise對象。

處理的方式有兩種:一是先行包裝Promise對象,使其始終返回一個(gè)成功的Promise。二是使用try.catch捕獲錯誤。

 
 
 
  1. // A1和A2都執(zhí)行成,且返回值為10。 
  2. A1().then(console.log); 
  3. A2().then(console.log); 
  4. async function A1() { 
  5. let n; 
  6. n = await createPromise(true); 
  7. return n; 
  8. async function A2() { 
  9. let n; 
  10. try { 
  11. n = await createPromise(false); 
  12. } catch (e) { 
  13. n = e; 
  14. return n; 
  15. function createPromise(needCatch) { 
  16. let p = new Promise((resolve, reject) => { 
  17. reject(10); 
  18. }); 
  19. return needCatch ? p.catch(err => err) : p; 

2.3 實(shí)現(xiàn)原理

前言中已經(jīng)提及,A函數(shù)是使用G函數(shù)進(jìn)行異步處理的增強(qiáng)版。既然如此,我們就從其改進(jìn)的方面入手,來看看其基于G函數(shù)的實(shí)現(xiàn)原理。A函數(shù)相對G函數(shù)的改進(jìn)體現(xiàn)在這幾個(gè)方面:更好的語義,內(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更為語義化。

 
 
 
  1. // G函數(shù) 
  2. function* request() { 
  3. let n = yield createPromise(); 
  4. // A函數(shù) 
  5. async function request() { 
  6. let n = await createPromise(); 
  7. function createPromise() { 
  8. return new Promise(resolve => { 
  9. setTimeout(() => { 
  10. resolve(10); 
  11. }, 1000); 
  12. }); 

內(nèi)置執(zhí)行器。調(diào)用A函數(shù)便會一步步自動執(zhí)行和等待異步操作,直到結(jié)束。如果需要使用G函數(shù)來自動執(zhí)行異步操作,需要為其創(chuàng)建一個(gè)自執(zhí)行器。通過自執(zhí)行器來自動化G函數(shù)的執(zhí)行,其行為與A函數(shù)基本相同??梢哉f,A函數(shù)相對G函數(shù)***改進(jìn)便是內(nèi)置了自執(zhí)行器。

 
 
 
  1. // 兩者都是每隔一秒鐘打印出10,重復(fù)兩次。 
  2. // A函數(shù) 
  3. A(); 
  4. async function A() { 
  5. let n1 = await createPromise(); 
  6. console.log(n1); 
  7. let n2 = await createPromise(); 
  8. console.log(n2); 
  9. // G函數(shù),使用自執(zhí)行器執(zhí)行。 
  10. spawn(G); 
  11. function* G() { 
  12. let n1 = yield createPromise(); 
  13. console.log(n1);
  14. let n2 = yield createPromise(); 
  15. console.log(n2); 
  16. function spawn(genF) { 
  17. return new Promise(function(resolve, reject) { 
  18. const gen = genF(); 
  19. function step(nextF) { 
  20. let next; 
  21. try { 
  22. next = nextF(); 
  23. } catch(e) { 
  24. return reject(e); 
  25. if(next.done) { 
  26. return resolve(next.value); 
  27. Promise.resolve(next.value).then(function(v) { 
  28. step(function() { return gen.next(v); }); 
  29. }, function(e) { 
  30. step(function() { return gen.throw(e); }); 
  31. }); 
  32. step(function() { return gen.next(undefined); }); 
  33. }); 
  34. function createPromise() { 
  35. return new Promise(resolve => { 
  36. setTimeout(() => {
  37. resolve(10); 
  38. }, 1000); 
  39. }); 

2.4 執(zhí)行順序

在了解A函數(shù)內(nèi)部與包含它外部間的執(zhí)行順序前,需要明白兩點(diǎn):一為Promise的實(shí)例方法是推遲到本輪事件末尾才執(zhí)行的后執(zhí)行操作,詳情請查看鏈接。二為Generator函數(shù)是通過調(diào)用實(shí)例方法來切換執(zhí)行權(quán)進(jìn)而控制程序執(zhí)行順序,詳情請查看鏈接。理解好A函數(shù)的執(zhí)行順序,能更加清楚的把握此三者的存在。

先看以下代碼,對比A1、A2和A3方法的結(jié)果。

 
 
 
  1. F(A1); // 接連打印出:1 3 4 2 5。
  2. F(A2); // 接連打印出:1 3 2 4 5。
  3. F(A3); // 先打印出:1 3 2,隔兩秒后打印出:4 9。
  4. function F(A) { 
  5. console.log(1); 
  6. A().then(console.log); 
  7. console.log(2); 
  8. async function A1() { 
  9. console.log(3); 
  10. console.log(4); 
  11. return 5; 
  12. async function A2() { 
  13. console.log(3); 
  14. let n = await 5; 
  15. console.log(4); 
  16. return n; 
  17. async function A3() { 
  18. console.log(3); 
  19. let n = await createPromise(); 
  20. console.log(4); 
  21. return n; 
  22. function createPromise() { 
  23. return new Promise(resolve => { 
  24. setTimeout(() => { 
  25. resolve(9); 
  26. }, 2000); 
  27. }); 

從結(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í)行器’這位大嬸吧?估計(jì)是忘記了。A函數(shù)的本質(zhì)就是帶有自執(zhí)行器的G函數(shù),所以探究A函數(shù)的執(zhí)行原理就是探究使用自執(zhí)行器的G函數(shù)的執(zhí)行原理。想起了?

再看下面代碼,使用相同邏輯的G函數(shù)會得到與A函數(shù)相同的結(jié)果。

 
 
 
  1. F(A); // 先打印出:1 3 2,隔兩秒后打印出:4 9。 
  2. F(() => { 
  3. return spawn(G); 
  4. }); // 先打印出:1 3 2,隔兩秒后打印出:4 9。 
  5. function F(A) { 
  6. console.log(1); 
  7. A().then(console.log); 
  8. console.log(2); 
  9. async function A() { 
  10. console.log(3); 
  11. let n = await createPromise(); 
  12. console.log(4); 
  13. return n; 
  14. function* G() { 
  15. console.log(3); 
  16. let n = yield createPromise(); 
  17. console.log(4); 
  18. return n; 
  19. function createPromise() { 
  20. return new Promise(resolve => { 
  21. setTimeout(() => { 
  22. resolve(9); 
  23. }, 2000); 
  24. }); 
  25. function spawn(genF) { 
  26. return new Promise(function(resolve, reject) { 
  27. const gen = genF(); 
  28. function step(nextF) { 
  29. let next; 
  30. try {
  31.  next = nextF(); 
  32. } catch(e) { 
  33. return reject(e); 
  34. if(next.done) { 
  35. return resolve(next.value); 
  36. Promise.resolve(next.value).then(function(v) { 
  37. step(function() { return gen.next(v); }); 
  38. }, function(e) { 
  39. step(function() { return gen.throw(e); }); 
  40. }); 
  41. }
  42. step(function() { return gen.next(undefined); }); 
  43. }); 

自動執(zhí)行G函數(shù)時(shí),遇到y(tǒng)ield命令后會使用Promise.resolve包裹其后的表達(dá)式,并為其設(shè)置回調(diào)函數(shù)。無論該P(yáng)romise是立刻有了結(jié)果還是過某段時(shí)間之后,其回調(diào)函數(shù)都會被推遲到在本輪事件末尾執(zhí)行。之后再是下一步,再下一步。同樣的道理適用于A函數(shù),當(dāng)遇到await命令時(shí)(此處略去三五字),所以有了如此這般的執(zhí)行順序。謝幕。


當(dāng)前名稱:Async:簡潔優(yōu)雅的異步之道
文章位置:http://www.dlmjj.cn/article/cosdiih.html