子非魚安知魚之樂
仔細想想也寫了1年多javascript了,從c#入坑JS後,裡裡外外也踩了不少坑。本質上還是兩種語言的思維不一樣。像之前c#,一個執行緒就是執行一件事情,在我看來大部分邏輯都是可以用順序,判斷和迴圈來解決的。所以一開始寫JS,對非同步,回撥,甚至promise都很懵圈。不過既然存在就有道理,畢竟當前執行執行緒一旦阻塞後那WEB頁面就卡死,一切就涼涼了。於是我也簡單整理了一下我接觸過的利用多執行緒和非同步分別去實現非阻塞I/O密集型任務都方式。
多執行緒模型開發
之前做WPF時,.net提供了一個BackgroundWorker的類庫。不得不說微軟開發體驗好,該提供都都提供了。。
MSDN對應BackgroundWorker文件
private void InitializeBackgroundWorker()
{
backgroundWorker1.DoWork += new DoWorkEventHandler (backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(
backgroundWorker1_ProgressChanged);
}
複製程式碼
DoWork 事件是執行都任務
RunWorkerCompleted 任務執行完後觸發都事件
ProgressChanged 執行中進度獲取
實際我們開發都時候,流程是這樣,原諒我靈魂畫手
uiandthread
用子執行緒去執行網路請求,讀寫檔案等操作,待子執行緒執行完畢,將結果直接返回給UI執行緒。如果UI執行緒需要進度條,也可以
非同步開發
javascript裡就簡單多了。打個比方
request('api', (res) => {
成功回撥
}, () => {
失敗回撥
});
複製程式碼
但是回撥用多了也噁心,有時候資料流邏輯上是同步使用,比如我必須得到這個資料結果才能下一步網路請求,下一步網路請求的結果拿來去做下下步請求。
request('api', (res) => {
成功回撥
request('api2', res, (res2) => {
成功回撥
request('api3', res2, (res) => {
成功回撥
}, () => {
失敗回撥
});
}, () => {
失敗回撥
});
}, () => {
失敗回撥
});
複製程式碼
所以Promise出現了。Promise,我個人簡單總結就是把巢狀邏輯擺平為同步程式碼邏輯。
request('api')
.then(res => {
return request('api2', res)
})
.then(res2 => {
return request('api3', res2)
})
.then(res3 => {
成功回撥
})
.catch(e => {
失敗回撥
});
複製程式碼
這樣程式碼看起來就沒那麼亂了。不過,這樣就最好了嘛?自然不是,既然都用到promise了,直接擼上await/async更是美滋滋。
async test() {
let res = await request('api');
let res2 = await request('api2', res);
let res3 = await request('api3', res2);
}
複製程式碼
注意:函式體內如果使用await關鍵字則該函式體必須用async標誌。這樣寫起來,跟寫java,c#等程式碼如出一轍了。(後來聽說C#也加上了async/await非同步方法)。對了,改為同步寫法後,我們對異常處理就更簡單了。
async test() {
try {
let res = await request('api');
let res2 = await request('api2', res);
let res3 = await request('api3', res2);
} catch(e) {
錯誤處理
}
}
複製程式碼
注意: try/catch只能捕捉同步程式碼的異常,如果寫成下面那樣,
test() {
try {
let res = request('api');
let res2 = request('api2', res);
let res3 = request('api3', res2);
} catch(e) {
錯誤處理
}
}
test2() {
try {
request('api')
.then(res => {
return request('api2', res)
})
.then(res2 => {
return request('api3', res2)
})
.then(res3 => {
成功回撥
})
.catch(e => {
失敗回撥
});
} catch(e) {
錯誤處理
}
}
複製程式碼
可是無法正確catch到異常喲。有時候寫nodejs時,出錯了需要重試,這樣用try/catch抓到異常後便可直接進行重試操作,遠比之前回撥方式要簡單的多。而且異常也可一直丟擲到外部,由外部呼叫程式碼去處理。
async main() {
try {
await test();
} catch(e) {
錯誤處理
await test();
}
}
async test() {
let res = await request('api');
let res2 = await request('api2', res);
let res3 = await request('api3', res2);
return res3
}
複製程式碼
總結
執行緒模型同步邏輯,異常捕捉與處理等都比非同步回撥方式要友好。但是執行緒建立,銷燬和切換是有開銷都。
JS的非同步回撥,免去執行緒都開銷。但是回撥程式碼讀起來也挺累的喲。如果大家用ES6和typescipt的話,用async/await的話就會友好很多。