什麼是非同步程式設計
什麼是非同步程式設計,非同步程式設計簡單來說就是:執行一個指令不會馬上返回結果而執行下一個任務,而是等到特定的事件觸發後,才能得到結果。
非同步程式設計時就需要指定非同步任務完成後需要執行的指令,總的來說有以下幾種“指定非同步指令”的方式:
- 屬性
- 回撥
- Promise
- Generator
- await,async
屬性
早期的javascript的非同步的實現也類似於這種類的屬性的方式:每個類例項的相關回撥事件有相應的handler(onclick,onchange,onload) 等。
在DOM0級事件處理程式,就是將一個函式賦值給一個元素的屬性。
element.onclick=function(){
console.log("clicked");
}
window.onload=function(){
console.log("loaded");
}
複製程式碼
回撥(釋出/訂閱)
由於javascript支援函數語言程式設計,JavaSCript語言對非同步程式設計的實現可以用回撥函式。 DOM2級事件解決了這個問題以上兩個問題
element.addEventListener("click",function(){
console.log("clicked");
});
複製程式碼
Promise
Promise很好的解決了"並行"的問題,我們看看用promise庫怎麼傳送get請求:
import fetch from 'node-fetch';
fetch('https://api.github.com')
.then((res)=>res.json())
.then((json)=>console.log("json:",json))
複製程式碼
可以看到promise把原來巢狀的回撥,改為級連的方式了,實際是一種代理(proxy)。
新建一個promise例項:
let promise = new Promise((resolve, reject)=>{
// 非同步操作的程式碼
if(/* 非同步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
複製程式碼
promise把成功和失敗分別代理到resolved 和 rejected . 同時還可以級連catch異常。
Generator
簡單來說generator可以理解為一個可遍歷的狀態機。
語法上generator,有兩個特徵:
- function關鍵字與函式名之前有一個星號。
- 函式體內部使用 yield關鍵字,定義不同的內部狀態。
由於generator是一個狀態機,所以需要手動呼叫 next才能執行,但TJ大神開發了co模組,可以自動執行generator。
import co from 'co';
co(function* (){
let now = Date.now();
yield sleep(150); // 約等待150ms
console.log(Date.now() - now);
});
function sleep(ms){
return function(cb){
setTimeout(cb, ms);
};
}
import fetch from 'node-fetch';
co(function* (){
let result = yield [
(yield fetch('https://api.github.com/users/1')).json(),
(yield fetch('https://api.github.com/users/2')).json(),
];
console.log("result:",result);
});
複製程式碼
無論是延遲執行,還是併發的從兩個介面獲取資料,generator都可以用同步的方式編寫非同步程式碼。
await,async(ECMAScript7)
ES7 引入了像C#語言中的 await,async關鍵字,而且babel已支援(引入plugins:transform-async-to-generator )
async函式完全可以看作多個非同步操作,包裝成的一個Promise物件,而await命令就是內部then命令的語法糖。
import fetch from 'node-fetch';
(async function (){
let result= await fetch('https://api.github.com/users/etoah');
let json =await result.json();
console.log("result:",json);
})();
//exception
(async function (){
try{
let result= await fetch('https://api.3github.com/users/etoah');
let json =await result.json();
console.log("result:",json);
}
catch(ex){
console.warn("warn:",ex);
}
})()
複製程式碼
簡單比較會發現,async函式就是將Generator函式的星號(*)替換成async,將yield替換成await,同時不需要co模組,更加語義化。
但是與yeild又不完全相同,標準沒有接收await*的語法( :( 檢視詳情),
若需“並行”執行promise陣列,推薦用Promise.All,所以需要並行請求時,需要這樣寫:
(async function (){
let result= await Promise.all([
(await fetch('https://api.github.com/users/tj')).json(),
(await fetch('https://api.github.com/users/etoah')).json()
]);
console.log("result:",result);
})();
複製程式碼
總結
到這裡,jser結合promise,yield,await的寫法,可以和回撥巢狀說拜拜了。