JavaScript異步調用的發展歷程

同步與異步

  • 一般,代碼是由上而下依次執行的。若是有多個任務,就必須排隊,前一個任務完成,後一個任務才能執行。這種連續的執行模式就叫作同步。
a();
b();
c();
複製代碼

上面代碼中,a、b、c是三個不一樣的函數,每一個函數都是一個不相關的任務。在同步模式會先執行 a 任務,再執行 b 任務,最後執行 c 任務。當b任務是一個耗時很長的請求時,而c任務是展示新頁面時,就會致使網頁卡頓。javascript

  • 所謂異步,就是一個任務不是連續完成的。好比,有一個讀取文件處理的任務,任務的第一段的向操做系統發出請求,要求讀取文件,而後程序執行其餘任務,等到操做系統返回文件,再去處理文件。這種不連續的執行模式就叫作異步。
a();
//當即發送請求
ajax('url',(b)=>{
    //請求回來執行
});
c();
複製代碼

上面代碼中,就是將b任務分紅了兩部分。一部分當即執行,另外一部分再請求回來後執行。也就解決了上面的問題。java

總結: 同步就是你們排隊工做,異步就是你們同時工做。node

異步的解決方案

一、CallBack

CallBack,即回調函數,回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。即異步操做執行完成後觸發執行的函數。jquery

//當請求完成時就會觸發回調函數
$.get('url',callback);
複製代碼

回調能夠完成異步操做,但用過 jquery 的 PE 都對下面的代碼折磨過。ajax

$.ajax({
    url:"type",
    data:1,
    success:function(a){
        url:"list",
        data:a,
        success:function(b){
            $.ajax({
                url:"content",
                data:b,
                success:function(c){
                    console.log(c);
                }
            })
        }
    }
})
複製代碼

上面代碼就是傳說中的回調金字塔,又叫回調地獄。這裏還只是單純的嵌套代碼,如若再加上業務代碼,代碼可讀性可想而知。本身開發起來還好,若是這是別人的代碼,你要改其中一部分足以讓人瘋掉。promise

二、事件發佈訂閱

咱們想讀取兩個文件時,但願這兩個文件都被讀取完後,拿到結果。咱們能夠經過 node 中的 EventEmitter 類來實現,它有兩個核心方法,一個是 on(表示註冊監聽事件),一個是emit(表示發射事件)。異步

let fs=require('fs');
let EventEmitter=require('event');
let eve=new EventEmitter();
let arr=[];//存儲讀取內容
//監聽數據獲取成功事件,而後調用回調函數
eve.on('ready',function(d){
    arr.push(d);
    if(arr.length==2){
        //兩個文件的數據
        console.log(arr);
    }
});
fs.readFile('./a.txt','utf8',function(err,data){
    eve.emit('ready',data);
});
fs.readFile('./b.txt','utf8',function(err,data){
    eve.emit('ready',data);
});
複製代碼

請求 a.txt 和 b.txt 文件數據,當成功後發佈ready事件。on 訂閱了 ready 事件,當監聽到觸發的時候,on 方法執行。async

哨兵變量

let fs=require('fs');
function after(times,callback){
    let arr=[];
    return function(d){
        arr.push(d);
        if(arr.length==times){
            callback(arr);
        }
    }
}
//2是一個哨兵變量,將讀取文件數據成功後執行的方法做爲回調函數傳給after方法
let out=after(2,function(data){
    console.log(data);
})
fs.readFile('./a.txt','utf8',function(err,data){
    out(data);
});
fs.readFile('./b.txt','utf8',function(err,data){
    out(data);
});
複製代碼

上面代碼 after 方法執行時傳入的 2 至關於一個哨兵變量,須要讀取幾個文件的數據就傳幾。將須要讀取的文件數量,和讀取所有文件成功後的方法做爲回調函數傳入 after。out 爲 after 執行後返回的函數,每次獲取文件成功後執行 out 方法能夠後去到最終所有文件的數據。函數

不使用回調地獄遇到的問題是:不知道讀取文件的函數何時執行完。只有當所有讀取完成後才能執行須要文件數據的方法。ui

三、Generator函數

Generator 函數要用*號來標識,yield 關鍵字表示暫停執行的標記。Generator 函數是一個狀態機,封裝了多個內部狀態。調用一次 next 就會繼續向下執行,返回的結果是一個迭代器,因此 Generator 函數仍是一個遍歷器對象生成函數。

function* read(){
    let a=yield '123';
    console.log(a);
    let b=yield 4;
    console.log(b);
}
let it = read();
console.log(it.next('321')); // {value:'123',done:false}
console.log(it.next(1)); // 1 {value:4,done:false}
console.log(it.next(2)); // 2 {value:2,done:true}
console.log(it.next(3)); // {value:undefined,done:true}
複製代碼

上面代碼能夠看出,調用 Generator 函數後,返回的不是函數運行的結果,而是一個指向內部狀態的指針對象,也就是遍歷器對象。必須調用遍歷器對象的 next 方法,讓指針移動的下一個狀態。內部指針就會從函數開始或上次定下來的地方開始執行,直到遇到下一個 yield 語句或 return 語句爲止。value 屬性表示當前的內部狀態值,是 yield 語句後面那個表達值;done 屬性是一個布爾值,表示是否遍歷結束。

四、Promise

在 JavaScript 的異步發展史中,出現了一系列解決 callback 弊端的庫,而 promise 成爲了勝者,併成功地被加入了ES6標準。Promise 函數接受一個函數做爲參數,該函數有兩個參數 resolve 和 reject。promise 就像一箇中介,而它只返回可信的信息給 callback,因此 callback 必定會被異步調用,且只會被調用一次。

let p=new Promise((resolve,reject)=>{
    //to do...
    if(/*異步操做成功*/){
        resolve(value);
    }else{
        reject(err);
    }
});

p.then((value)=>{
    //success
},(err)=>{
    //failure
})

複製代碼

這樣 Promise 就解決了回調地獄的問題,好比咱們連續讀取多個文件時,寫法以下:

let fs=require('fs');
function read(url){
    return new Promise((resolve,reject)=>{
        fs.readFile(url,'utf8',function(err,data){
            if(err) reject(err);
            resolve(data);
        })
    }
}

read('a.txt').then((data)=>{
    console.log(data);
}).then(()=>{
    return read('b.txt');
}).then((data)=>{
    console.log(data);
}).catch((err)=>{
    console.log(err);
})
複製代碼

如此不斷的返回一個新的 Promise,這種不斷的鏈式調用,就擺脫了callback回調地獄的問題和異步代碼非線性執行的問題。

Promise 還解決了 callback 只能捕獲當前錯誤異常。Promise 和 callback 不一樣,Promise 代理着全部的 callback 的報錯,能夠由 Promise 統一處理。因此,能夠經過catch來捕獲以前未捕獲的異常。

Promise解決了callback的回調地獄問題,但Promise並無擺脫callback。因此,有沒有更好的寫法呢?

五、Async Await

async函數是ES7中的一個新特性,它結合了Promise,讓咱們擺脫callback的束縛,直接用類同步的線性方式寫異步函數,使得異步操做變得更加方便。

let fs=require('fs');
function read(url){
    return new Promise((resolve,reject)=>{
        fs.readFile(url,'utf8',function(err,data){
            if(err) reject(err);
            resolve(data);
        })
    }
}

async function r(){
    let a=await read('a.txt');
    let b=await read('b.txt');
    return a+b;
}
r().then((data)=>{
    console.log(data);
});

複製代碼

至此,異步的 await 函數已經可讓咱們滿意。目前使用 Babel 已經支持 ES7 異步函數的轉碼了,你們能夠在本身的項目中開始嘗試。之後會不會出現更優秀的方案?以咱們廣大程序羣體的創造力,相信必定會有的。

JavaScript異步調用的發展歷程就到這裏了,若是您以爲文章有用,快去點贊(⊙﹏⊙)!
相關文章
相關標籤/搜索