紀錄一下 javascript 中非同步的寫法範例
非同步範例 01 Promise Sample Page 01
使用 new Promise(resolve,reject)
去模擬三個事件的執行順序,可以想見若未來還有其他事件要增加,最終的寫法肯定會越來越像氣功波,且難以維護
在事件內返回的是一個 Promise
,以便讓後續的 then()
語法可以接續下去,setTimeout()
是用來模擬 ajax 或是其他需要執行較長時間的程序,在程序結束完畢之後,透過resolve()
改變 Promise 的狀態為執行成功,這樣子在外面的執行事件偵測到就會往下執行then()
區塊
假設第一個事件是透過 ajax 取得後端資料,取得資料後需要再後續步驟接續處理資料,可以將資料傳入給resolve()
函式,這樣子在後續的then()
就可以作為輸入參數來使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 work01 (ms ) { return new Promise ((resolve, reject ) => { setTimeout (ms => { let time = new Date (); resolve ({ title, time }); }, ms); }); }, this .work01 (3000 ).then (info => { this .showInfo (info); this .work02 (2000 ).then (info => { this .showInfo (info); this .work03 (1000 ).then (info => { this .showInfo (info); }); }); });
02 async / await Sample Page 02
事件方法與先前使用Promise
並無不同,但是在執行事件的方法中,需要宣告為async
表示此方法為一個非同步方法,並於該方法之內,將需要等候執行完成的方法前端加上await
表示此方法需要等候執行結束才可以往下繼續執行,使用語法糖來改寫Promise
的最大好處是:程式碼階層數量不再受到事件數量影響,便於閱讀也容易維護、撰寫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async doJob ( ) { const r1 = await this .work01 (3000 ); this .showInfo (r1); const r2 = await this .work02 (2000 ); this .showInfo (r2); const r3 = await this .work03 (1000 ); this .showInfo (r3); } async doJob ( ) { this .showInfo (await this .work01 (3000 )); this .showInfo (await this .work02 (2000 )); this .showInfo (await this .work03 (1000 )); }
03 Promise catch error Sample Page 03
若要加上錯誤處理,則是添加上reject()
區段,並於執行事件的方法中加上catch()
來捕捉例外
jquery ajax API 返回的就是 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 new Vue ({ el : "#app" , data : { infos : [], }, methods : { ajaxPromise (params ) { return $.ajax ({ type : params.type || "get" , async : params.async || true , url : params.url , data : params.data || "" , }); }, setData (data ) { this .infos = data; }, async doJob ( ) { this .ajaxPromise ({ url : "dataNotExist.json" }) .then ((data ) => { this .setData (data); }) .catch ((err ) => { console .log (err); }); }, }, mounted ( ) { this .doJob (); }, });
04 async / await catch error Sample Page 04
若改寫成 async / await
,Promise
發生錯誤時會拋出reject()
的異常,因此在執行事件中需要透過try...catch...
來處理
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 new Vue ({ el : "#app" , data : { infos : [], }, methods : { ajaxPromise (params ) { return $.ajax ({ type : params.type || "get" , async : params.async || true , url : params.url , data : params.data || "" , }); }, setData (data ) { this .infos = data; }, async doJob ( ) { try { let data = await this .ajaxPromise ({ url : "dataNotExist.json" }); this .setData (data); } catch (error) { console .log (error); } }, }, mounted ( ) { this .doJob (); }, });
實際經驗 因為對非同步真的不熟,在實務上也犯了將$.ajax
再包一層 promise 卻發現無法正確工作,查了很久才發現這件事情,特別紀錄一下
使用$.ajax
語法就已經是回傳非同步的 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 { function makeId (length ) { var result = "" ; var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ; var charactersLength = characters.length ; for (var i = 0 ; i < length; i++) { result += characters.charAt (Math .floor (Math .random () * charactersLength)); } return result; } function test (params ) { let id = makeId (3 ); console .log (`${id} start` ); return $.ajax ({ url : "http://localhost:40001/mock/8/Common/GetZipCode" , method : "POST" , }).then (() => { console .log (`${id} end` ); }); } async function init ( ) { await test (); await test (); } init (); }