深刻理解javascript系列(十九):從Promise開始到async/await

什麼是同步與異步的定義,在這裏我就不作記錄,直接用代碼來表示它們之間的區別。api

首先使用Promise模擬一個發起請求的函數,該函數執行後,會在1s以後返回數值30。數組

function fn() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(30);
        }, 1000);
    })
}複製代碼

在該函數的基礎上,咱們也可使用async/await語法來模擬同步效果。瀏覽器

var foo = async function() {
    var t = await fn();
    console.log(t);
    console.log('next');
}

foo();複製代碼

輸出結果爲:bash

Promise {<pending>} //1s 以後依次輸出
test:11 30
test:12 next
複製代碼

而異步效果則會有不一樣的輸出結果:異步

var foo = function() {
    fn().then(function(res) {
        console.log(res);
    });
    console.log('next');
}複製代碼

輸出結果:async

next
// 1s後
30複製代碼

好了,接下來咱們正式開始記錄Promise函數

Promise

1.  Ajax

Ajax是網頁與服務端進行數據交互的一種技術。咱們能夠經過服務端提供的接口,用Ajax向服務端請求咱們須要的數據。過程以下:工具

//簡單的Ajax原生實現

//服務端接口
var url = 'api/xxxx';
var result;

var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();

XHR.onreadystatechange = function() {
    if(XHR.readyState == 4 && XHR.status == 200) {
        result = XHR.response;
    }
}

複製代碼

這樣看上去並無什麼問題。可是若是這個時候,還須要作另外一個Ajax請求,那麼這個新的Ajax請求中的一個參數,則必須從上一個Ajax請求中獲取,這個時候咱們就不得不就得在result獲得後在進行一次請求。ui

當第三個Ajax(甚至更多)仍然依賴上一個請求的時候,此時的代碼就變成了一場災難。咱們須要不停地嵌套回調函數,以確保下一個接口所須要的參數的正確性,這樣的災難,咱們稱爲回調地獄。url

因此隨着發展,就出現了Promise,他能解決這個問題。

咱們想要確保某代碼在某某以後執行時,能夠利用函數調用棧,將想要執行的代碼放入回調函數中(這是利用同步阻塞)。

function a(callback) {
    console.log('先結婚')
    callback();
}

function b() {
    console.log('再生孩子')
}
a(b);複製代碼

插個題外話:「瀏覽器最先內置的setTimeout與setInterval就是基於回調的思想實現的」。

可是這裏也有一個問題,咱們想要在a中執行的代碼必須如今callback以前才能輸出咱們想輸出的。那該怎麼辦?

其實問題很好解決,除了利用函數調用棧的執行順序外,還能夠利用隊列機制來確保咱們想要的代碼壓後執行。

function a(callback) {
    //將想要執行的代碼放入隊列中後,根據事件循環機制,
    //就不用把它放到最後面了。
    callback && setTimeout(callback, 0);
    console.log('先結婚')

}

function b() {
    console.log('再生孩子')
}
a(b);複製代碼

與setTimeout相似,Promise也能夠認爲是一種任務分發器,它將任務分配到Promise隊列中,一般的流程是首先發起一個請求,而後等待(等待時間無法肯定)並處理請求結果。

var tag = true;
var p = new Promise(function(resolve, reject) {
    if(tag) {
        resolve('tag is true')
    } else {
        reject('tag is false')
    }
})

p.then(function(result) {
    console.log(result);
})
.catch(function(err) {
    console.log(err);
})複製代碼

下面簡單介紹一下Promise的相關基礎知識:

  • new Promise表示建立一個Promise實例對象。
  • Promise函數中的第一參數爲一個回調函數,也能夠稱之爲executor。一般狀況下,在這個函數中,會執行發起請求操做,並修改結果的狀態值。
  • 請求結果有三種狀態,分別是pending(等待中,表示尚未獲得結果)、resolved(獲得了咱們想要的結果,能夠繼續執行),以及rejected(獲得了錯誤的,或者不是咱們指望的結果,拒絕繼續執行)。請求結果的默認狀態爲pending。在executor函數中,能夠分別使用resolve與rejected將狀態修改成對應的resolved與rejected。resolve、reject是executor函數的兩個參數,它們可以將請求結果的具體數據傳遞出去。
  • Promise實例擁有的then方法,能夠用來處理當請求結果的狀態變成resolved時的邏輯。then的第一個參數爲一個回調函數,該函數的參數是resolve傳遞出來的數據。在上面的例子中,result = tag is true。
  • Promise實例擁有的catch方法,可用來處理當前請求結果的狀態變成rejectd時的邏輯。catch的第一個參數爲一個回調函數,該函數的參數是一個reject傳遞出來的數據。在上面的例子中,err = tag is false。
下面經過例子來感覺一下Promise的用法。

//demo01.js
function fn(num) {
    //建立一個Promise實例
    return new Promise(function(resolve, reject) {
        if(typeof num == 'number') {
           //修改結果狀態值爲resolved
           resolve();
        } else {
            // 修改結果狀態值爲rejected
            reject();
        }
    }).then(function() {
        console.log('參數是一個number值');
    }).catch(function() {
        console.log('參數不是一個number值');
    })
}

//修改參數的類型,觀察輸出的結果
fn('12');

//注意觀察該語句的執行順序
console.log('next code');複製代碼

then方法能夠接收兩個參數,第一個參數用來處理resolved狀態的邏輯,第二個參數用來處理rejected狀態的邏輯。

then方法由於返回的還是一個Promise實例對象,所以then方法能夠嵌套使用。在這個過程當中,經過在內部函數末尾return的方式,可以將數據持續日後傳遞。

下面咱們來對Ajax進行一個簡單的封裝。

var url = 'api/xxxx';

//封裝一個get請求的方法
function getJSON(url) {
    return new Promise(function(resolve, reject) {
        //利用Ajax發送一個請求
        var XHR = new XMLHttpRequest();
        XHR.open('GET', url, true);
        XHR.send();

        //等待結果
        XHR.onreadystatechange = function() {
            if(XHR.readyState == 4) {
                if(XHR.status == 200) {
                    try {
                        var res = JSON.parse(XHR.responseText);
                        // 獲得正確的結果修改狀態並將數據傳遞出去
                        resolve(response);
                    } catch(e) {
                        reject(e)
                    }
                } else {
                    // 獲得錯誤的結果並拋出異常
                    reject(new Error(XHR.statusText));
                }
            }
        }
    })
}


//封裝好之後,使用就很簡單了
getJSON(url).then(function(res){
    console.log(res)
})複製代碼

2.  Promise.all

當有一個Ajax請求,它的參數須要另外兩個甚至更多個請求都有返回結果以後才能肯定時,就須要用到Promise.all來幫助咱們應對這個場景。

Promise.all接收一個Promise對象組成的數組做爲參數,當這個數組中全部的Promise對象狀態都變成resolved或者rejected時,它纔會去調用then方法。

var url1 = 'xxx';
var url2 = 'xxxxx';

function renderAll() {
    return Promise.all([getJSON(url1), getJSON(url2)]);
}

renderAll().then(function(value) {
    console.log(value);
})複製代碼

3.  Promise.race

與Promise.all類似的是,Promise.race也是一個Promise對象組成的數組做爲參數,不一樣的是,只要當數組中的其中一個Promise狀態變成了resolved或者rejected時,就能夠調用then方法。

async/await

異步問題不只能夠用Promise,還能夠用async/await,都說這是終極解決方案。

async/await是ES7中新增的語法,雖然如今有些瀏覽器已經支持了該語法,但在實際使用中,仍然須要在構建工具中配置對該語法的支持才能放心使用。

在函數聲明的前面,加上關鍵字async,這就是async的具體使用。

async function fn() {
    return 30;
}

//或者
const fn = async ()=> {
    return 30;
}

console.log(fn());

//打印結果
Promise {<resolved>: 30}__proto__:Promise[[PromiseStatus]]:"resolved"[[PromiseValue]]:30
複製代碼

能夠發現打印結果是一個Promise對象,所以能夠猜到async實際上是Promise的一個語法糖,目的是爲了讓寫法更加簡單,所以也可使用Promise的相關語法來處理後續的邏輯。

fn().then(res=>{
    console.log(res);
})複製代碼

await的含義是等待,意思就代碼須要等待await後面的函數運行完而且有了返回結果以後,才繼續執行下面的代碼。這正是同步的效果。

可是須要注意的是,await關鍵字只能在async函數中使用,而且await後面的函數運行後必須返回一個Promise對象才能實現同步的效果。

當使用一個變量去接收await的返回值時,該返回值爲Promise中resolve傳遞出來的值,也就是PromiseValue。

爲了切實感覺下async/await的用法。咱們結合實際開發中最常遇到的異步請求接口的場景。

//先定義接口請求的方法,因爲jQuery封裝的幾個請求方法都是返回Promise實例。
//所以能夠直接使用async/await函數實現同步

const getUserInfo = () => $.get('api/asdsd');

const clickHandler = async ()=>{
    try{
        const res = await getUserInfo();
        console.log(res);
        
        // do something
    } catch(e){
        //處理錯誤邏輯
    }
}複製代碼

爲了保證邏輯的完整性,在實踐中try/catch必不可少。

相關文章
相關標籤/搜索