ES6 Promise的使用和理解

JS的異步

JS語言的執行環境是「單線程」的,即指一次只能完成一件任務;若是有多個任務,那麼必須排隊,前面一個任務完成,再執行後一個任務,以此類推。這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段Javascript代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。node

爲了解決這個問題,Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。git

"同步模式"就是上一段的模式,後一個任務等待前一個任務結束,而後再執行,程序的執行順序與任務的排列順序是一致的、同步的;"異步模式"則徹底不一樣,每個任務有一個或多個回調函數(callback),前一個任務結束後,不是執行後一個任務,而是執行回調函數,後一個任務則是不等前一個任務結束就執行,因此程序的執行順序與任務的排列順序是不一致的、異步的。es6

"異步模式"很是重要。在瀏覽器端,耗時很長的操做都應該異步執行,避免瀏覽器失去響應,最好的例子就是Ajax操做。在服務器端,"異步模式"甚至是惟一的模式,由於執行環境是單線程的,若是容許同步執行全部http請求,服務器性能會急劇降低,很快就會失去響應。github

經常使用的異步編程模式

  1. 回調函數
    即f1,f2兩個函數,f2要等待f1執行結果後執行,即 f1(f2)
  2. 事件驅動的方式
    f1.on('done', f2); (JQ寫法,f1完成時,trigger("done")則執行f2)
  3. 發佈-訂閱 模式
  4. Promise對象實現

Promise對象

本文着重講ES6的Promise對象的定義和用法 阮一峯老師的ES6詳解 - Promise對象 相信你們在學習ES6的過程當中都或多或少的學習過阮老師的ES6教程,那麼這裏簡單舉一些例子講述Promise對象的特色和使用方法npm

基礎使用方法

ES6提供Promise構造函數,咱們創造一個Promise實例,Promise構造函數接收一個函數做爲參數,這個傳入的函數有兩個參數,分別是兩個函數 resolvereject做用是,resolve將Promise的狀態由未成功變爲成功,將異步操做的結果做爲參數傳遞過去;類似的是reject則將狀態由未失敗轉變爲失敗,在異步操做失敗時調用,將異步操做報出的錯誤做爲參數傳遞過去。
實例建立完成後,可使用then方法分別指定成功或失敗的回調函數,比起f1(f2(f3))的層層嵌套的回調函數寫法,鏈式調用的寫法更爲美觀易讀編程

let promise = new Promise((resolve, reject)=>{
    reject("拒絕了");
});
promise.then((data)=>{
    console.log('success' + data);
}, (error)=>{
    console.log(error)
});

執行結果:"拒絕了"

複製代碼

Promise的特色

  • 對象不受外界影響,初始狀態爲pending(等待中),結果的狀態爲resolve和reject,只有異步操做的結果決定這一狀態
  • 狀態只能由pending變爲另外兩種的其中一種,且改變後不可逆也不可再度修改,
    即pending -> resolved 或 pending -> reject
let promise = new Promise((resolve, reject)=>{
    reject("拒絕了");
    resolve("又經過了");
});
promise.then((data)=>{
    console.log('success' + data);
}, (error)=>{
    console.log(error)
});

執行結果: "拒絕了"

複製代碼

上述代碼不會再執行resolve的方法數組

then方法的規則

  • then方法下一次的輸入須要上一次的輸出
  • 若是一個promise執行完後 返回的仍是一個promise,會把這個promise 的執行結果,傳遞給下一次then
  • 若是then中返回的不是Promise對象而是一個普通值,則會將這個結果做爲下次then的成功的結果
  • 若是當前then中失敗了 會走下一個then的失敗
  • 若是返回的是undefined 無論當前是成功仍是失敗 都會走下一次的成功
  • catch是錯誤沒有處理的狀況下才會走
  • then中不寫方法則值會穿透,傳入下一個then

用node fs模塊讀取文件的流程來測試 咱們建立一個讀取文件的方法,在Promise中定義若是讀取成功則展現文件的內容,不然報出錯誤promise

let fs = require('fs');

function read(file, encoding) {
    return new Promise((resolve, reject)=>{
        fs.readFile(filePath, encodeing, (err, data)=> {
            if (err) reject(err);
            resolve(data);
        });
    })
}
複製代碼

因爲想看到屢次連貫回調,咱們專門設置3個txt文件,其中1號文件的內容爲2號文件的文件名,2號文件的內容爲3號文件的文件名,3號中展現最終內容瀏覽器

執行代碼以下:緩存

read('1.promise/readme.txt', 'utf8').then((data)=>{
    console.log(data)
});
複製代碼

讀取一個文件的打印結果爲,readme2.txt
咱們改造這個代碼,添加多個回調,在最後一個以前的全部then中都return出當前返回的promise對象

read('readme.txt', 'utf8').then((data)=>{
    return read(data, 'utf8');
}).then((data)=>{
    return read(data, 'utf8')
}).then((data)=>{
    console.log(data);
});

最終輸出 readme3.txt的內容

複製代碼

再對下一步then進行新的處理,咱們對readme3.txt的內容進行加工並返回

read('readme.txt', 'utf8').then((data)=>{
    return read(data, 'utf8');
}).then((data)=>{
    return read(data, 'utf8')
}).then(data=>{
    return data.split('').reverse().join(); // 這一步返回的是一個普通值,普通值在下一個then會做爲resolve處理
}).then(null,data=>{   // 特地不對成功作處理,放過這一個值,進入下一步
    throw new Error('出錯')  // 因爲上一步返回的是普通值,走成功回調,不會走到這裏
}).then(data=>{
    console.log(data)  // 最終會打印出來上上步的普通值
});
複製代碼

這裏咱們將內容處理後,則將一個普通值傳給了下次的then,可是因爲下一個then沒有處理成功方法(null)

這個普通值會繼續傳入下一個then,最終會做爲成功值打印出來。

最後咱們看一下對錯誤的處理,在處理完readme3.txt的結果後,咱們將這個值傳入下一個then中,令其做爲一個文件名打開,然而此時已經找不到這個不存在的文件了,那麼在最後一步就會打印出報錯的結果

read('readme.txt', 'utf8').then((data)=>{
    return read(data, 'utf8');
}).then((data)=>{
    return read(data, 'utf8')
}).then(data=>{
    return data.split('').reverse().join();
}).then(null,data=>{
    throw new Error('出錯')
}).then(data=>{
    return read(data, 'utf8')
}).then(null,(err)=>{
    console.log(err)
});
複製代碼

結果:

Promises A+ (Promises Aplus)

Promises Aplus規範即規定了Promise的原理,源代碼規範等,經過這個規範,咱們能夠本身實現一個基於PromiseA+規範的Promise類庫,下面咱們展現一下源碼的實現

/**
 * Promise 實現 遵循promise/A+規範
 * 官方站: https://promisesaplus.com/
 * Promise/A+規範譯文:
 * https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4
 */

// promise 三個狀態
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function Promise(excutor) {
    let self = this; // 緩存當前promise實例對象
    self.status = PENDING; // 初始狀態
    self.value = undefined; // fulfilled狀態時 返回的信息
    self.reason = undefined; // rejected狀態時 拒絕的緣由
    self.onFulfilledCallbacks = []; // 存儲fulfilled狀態對應的onFulfilled函數
    self.onRejectedCallbacks = []; // 存儲rejected狀態對應的onRejected函數

    function resolve(value) { // value成功態時接收的終值
        if(value instanceof Promise) {
            return value.then(resolve, reject);
        }

        // 爲何resolve 加setTimeout?
        // 2.2.4規範 onFulfilled 和 onRejected 只容許在 execution context 棧僅包含平臺代碼時運行.
        // 這裏的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。

        setTimeout(() => {
            // 調用resolve 回調對應onFulfilled函數
            if (self.status === PENDING) {
                // 只能由pedning狀態 => fulfilled狀態 (避免調用屢次resolve reject)
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilledCallbacks.forEach(cb => cb(self.value));
            }
        });
    }

    function reject(reason) { // reason爲失敗態時接收的緣由
        setTimeout(() => {
            // 調用reject 回調對應onRejected函數
            if (self.status === PENDING) {
                // 只能由pedning狀態 => rejected狀態 (避免調用屢次resolve reject)
                self.status = REJECTED;
                self.reason = reason;
                self.onRejectedCallbacks.forEach(cb => cb(self.reason));
            }
        });
    }

    // 捕獲在excutor執行器中拋出的異常
    // new Promise((resolve, reject) => {
    //     throw new Error('error in excutor')
    // })
    try {
        excutor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}


複製代碼

這一部分代碼咱們對resolve和reject進行了判斷處理,接着咱們構造then方法

/**
 * [註冊fulfilled狀態/rejected狀態對應的回調函數]
 * @param  {function} onFulfilled fulfilled狀態時 執行的函數
 * @param  {function} onRejected  rejected狀態時 執行的函數
 * @return {function} promise2  返回一個新的promise對象
 */
Promise.prototype.then = function (onFulfilled, onRejected) {
    // 成功和失敗的回調 是可選參數
    
    // onFulfilled成功的回調 onRejected失敗的回調
    let self = this;
    let promise2;
    // 須要每次調用then時都返回一個新的promise
    promise2 = new Promise((resolve, reject) => {
    // 成功態
        if (self.status === 'resolved') {
            setTimeout(()=>{
                try {
                    // 當執行成功回調的時候 可能會出現異常,那就用這個異常做爲promise2的錯誤的結果
                    let x = onFulfilled(self.value);
                    //執行完當前成功回調後返回結果多是promise
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        // 失敗態
        if (self.status === 'rejected') {
            setTimeout(()=>{
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        if (self.status === 'pending') {
           // 等待態時,當一部調用resolve/reject時,將onFullfilled/onReject收集暫存到集合中
           self.onResolvedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
            self.onRejectedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
        }
    });
    return promise2
}
// 其中規範要求對回調中增長setTimeout處理

複製代碼

能夠看到resolve和reject都有一個處理新promise的方法resolvePromise,對其進行封裝,達處處理不一樣狀況的目的

/**
 * 對resolve 進行改造加強 針對resolve中不一樣值狀況 進行處理
 * @param  {promise} promise2 promise1.then方法返回的新的promise對象
 * @param  {[type]} x         promise1中onFulfilled的返回值
 * @param  {[type]} resolve   promise2的resolve方法
 * @param  {[type]} reject    promise2的reject方法
 */
function resolvePromise(promise2,x,resolve,reject){
    if(promise2 === x){ // 若是從onFullfilled中返回的x就是promise2,就會致使循環引用報錯
        return reject(new TypeError('Chaining cycle'));
    }
    let called; // 聲明避免屢次使用
    // x類型判斷 若是是對象或者函數
    if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
    // 判斷是不是thenable對象
        try{
            let then = x.then; 
            if(typeof then === 'function'){
                then.call(x,y=>{ 
                    if(called) return; 
                    called = true;
                    resolvePromise(promise2,y,resolve,reject);
                },err=>{ 
                    if(called) return;
                    called = true;
                    reject(err);
                });
            }else{
            // 說明是普通對象/函數
                resolve(x);
            }
        }catch(e){
            if(called) return;
            called = true;
            reject(e);
        }
    }else{ 
        resolve(x);
    }
}
複製代碼

以上基本實現了Promise的基本方法,根據Promise的用法,補充一些類上的方法

// 用於promise方法鏈時 捕獲前面onFulfilled/onRejected拋出的異常
Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason);
    })
}
Promise.resolve = function(value){
    return new Promise((resolve,reject)=>{
        resolve(value);
    })
}
Promise.prototype.catch = function(onRejected){
    // 默認不寫成功
    return this.then(null,onRejected);
};
/**
 * Promise.all Promise進行並行處理
 * 參數: promise對象組成的數組做爲參數
 * 返回值: 返回一個Promise實例
 * 當這個數組裏的全部promise對象所有變爲resolve狀態的時候,纔會resolve。
 */
Promise.all = function(promises){
    return new Promise((resolve,reject)=>{
        let arr = [];
        let i = 0;
        function processData(index,data){
            arr[index] = data;
            if(++i == promises.length){
                resolve(arr);
            }
        }
        for(let i = 0;i<promises.length;i++){
            promises[i].then(data=>{ // data是成功的結果
                processData(i,data);
            },reject);
        }
    })
}
/**
 * Promise.race
 * 參數: 接收 promise對象組成的數組做爲參數
 * 返回值: 返回一個Promise實例
 * 只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行後面的處理(取決於哪個更快)
 */
Promise.race = function(promises){
    return new Promise((resolve,reject)=>{
        for(let i = 0;i<promises.length;i++){
            promises[i].then(resolve,reject);
        }
    })
}

複製代碼

最後咱們導出方法

module.exports = Promise;
複製代碼

至此一個符合PromiseA+規範的本身寫的源碼庫完成了,能夠測試使用這個庫替代Promise,以測試是否有邏輯錯誤等,或者可使用

npm install promises-aplus-tests -g
promises-aplus-test 文件名
複製代碼

插件來測試該源碼是否符合PromiseA+規範

但願這篇文章能幫到你,以上

相關文章
相關標籤/搜索