JavaScript非同步史

still6發表於2018-03-17

什麼是非同步程式設計

什麼是非同步程式設計,非同步程式設計簡單來說就是:執行一個指令不會馬上返回結果而執行下一個任務,而是等到特定的事件觸發後,才能得到結果。

非同步程式設計時就需要指定非同步任務完成後需要執行的指令,總的來說有以下幾種“指定非同步指令”的方式:

  1. 屬性
  2. 回撥
  3. Promise
  4. Generator
  5. 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,有兩個特徵:

  1. function關鍵字與函式名之前有一個星號。
  2. 函式體內部使用 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的寫法,可以和回撥巢狀說拜拜了。

相關文章