如何實現一個符合promiseA+規範的promise

acdseen發表於2018-04-06

前言

Promise 是非同步程式設計的一種解決方案: 從語法上講,promise是一個物件,從它可以獲取非同步操作的訊息;從本意上講,它是承諾,承諾它過一段時間會給你一個結果。 promise有三種狀態:pending(等待態),fulfiled(成功態),rejected(失敗態);狀態一旦改變,就不會再變。創造promise例項後,它會立即執行。

編寫符合promiseA+規範的promise實現

在實現之前,可以先看一下Promise A plus規範

1. 建立promise建構函式


這裡先實現promise最基本的功能:promise建立後立即執行;在then時執行相應的函式;捕獲錯誤立即變成reject態

// promise裡只有一個引數,叫executor(執行器)
function Promise(executor) {
    let self = this;
    self.status = 'pending';//等待態
    self.value = undefined;//預設成功的值
    self.err = undefined;//預設失敗的值
    function resolve(value) {
        if (self.status === 'pending') {
            self.status = 'resolved';
            self.value = value;
        }
    }
    function reject(err) {
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.err = err;
        }
    }
    try {//捕獲時發生異常,直接變成reject態,丟擲錯誤
        executor(resolve, reject);//promise例項建立後,立即執行
    } catch (error) {
        reject(error);  
    }
}
//在prototype上定義then例項方法
Promise.prototype.then = function (onFulfilled, onRejected) {
    let self = this;
    if (self.status === 'resolved') {
        onFulfilled(self.value);
    }
    if (self.status === 'rejected') {
        onRejected(self.err);
    }
}
複製程式碼

這裡我們先測試一下我們的Promise

如何實現一個符合promiseA+規範的promise

這裡便實現了基本功能,前面說過Promise 是非同步程式設計的一種解決方案; 我們加個非同步邏輯執行一下:

如何實現一個符合promiseA+規範的promise

2. Promise非同步呼叫


我們都知道非同步程式碼並不會立即執行,這時既不是resolved也不是rejected,而是pending

在之前的狀態判斷裡面,正好丟了一個pending狀態。

OK,這時需要在then裡判斷當status為pending時,先將onFulfilled, onRejected存入陣列裡,當status改變時,再遍歷陣列讓裡面的函式依次執行,看程式碼。

(1)申明兩個存放onFulfiled(),onRejected()的陣列

function Promise(resolver) {
    let self = this;
    self.status = 'pending';//等待態
    self.value = undefined;//預設成功的值
    self.err = undefined;//預設失敗的值
    self.onResolvedCallbacks = []; // 存放then成功的回撥
    self.onRejectedCallbacks = []; // 存放then失敗的回撥
    function resolve(value) {
        if (self.status === 'pending') {
            self.status = 'resolved';
            self.value = value;
            self.onResolvedCallbacks.forEach(fn=>{//呼叫resolve時,依次執行陣列裡的函式
                fn();
            })
        }
    }
    function reject(err) {
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.err = err;
            self.onRejectedCallbacks.forEach(fn=>{
                fn();
            })
        }
    }
    try {//捕獲時發生異常,直接丟擲錯誤
        resolver(resolve, reject);//promise例項建立後,立即執行它的方法
    } catch (error) {
        reject(error)
    }
}
複製程式碼

(2)接著在then方法裡新增pending的判斷

Promise.prototype.then = function (onFulfilled, onRejected) {
    let self = this;
    if (self.status === 'resolved') {
        onFulfilled(self.value);
    }
    if (self.status === 'rejected') {
        onRejected(self.err);
    }
    if(self.status==='pending'){// 此時沒resolved,也沒rejectd
        self.onResolvedCallbacks.push(()=>{
            onFulfilled(self.value);
        });
        self.onRejectedCallbacks.push(()=>{
            onRejected(self.err);
        })
    }
}
複製程式碼

再看剛剛的非同步邏輯

如何實現一個符合promiseA+規範的promise

1s後就執行成功了,是不是很神奇,再看下面:

3. Promise鏈式呼叫


(1)規範裡說在同一個promise裡then可以被多次呼叫。

如何實現一個符合promiseA+規範的promise

(2)jquery能實現鏈式呼叫靠的是返回this,而promise不能返回this,規範裡又說它返回的是一個新的Promise例項 (注意,不是原來那個Promise例項)

如何實現一個符合promiseA+規範的promise

在then裡新建一個promise2併為每一個狀態包一個Promise

如何實現一個符合promiseA+規範的promise

這裡就返回了一個新的promise2,在promise2裡也需要呼叫resolve或者reject;這裡申明一個 x 來儲存上一次then的返回值 規範裡說只要上一次then有返回值,下一次then一定呼叫成功態resolve(x) 再來看看規範,規範裡說道

(1)x可能是一個promise;

如何實現一個符合promiseA+規範的promise

(2)可能是一個物件或者方法;

如何實現一個符合promiseA+規範的promise

(3)也有可能是一個普通的值

如何實現一個符合promiseA+規範的promise

這時需要一個方法來處理x

3.1 對onFulfilled和onRejected的返回值進行處理


於是引入一個處理方法resolvePromise(promise2, x, resolve, reject); 這裡四個引數分別是

  1. Promise2是我們返回的新的promise
  2. x是上一次then的返回值
  3. resolve是成功的方法
  4. reject是失敗的方法

這裡需要注意一下,有些人寫的promise可能會既呼叫成功,又呼叫失敗,如果兩個都呼叫先呼叫誰另一個就忽略掉。 在resolvePromise(promise2, x, resolve, reject)裡增加一個判斷called表示是否呼叫過成功或者失敗,看程式碼:

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {//promise2和x不能相同
        return reject(new TypeError('迴圈引用了'))
    }
    let called;// 表示是否呼叫過成功或者失敗
    //這裡對x的型別進行判斷
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {  // 判斷x是不是promise,如果x是物件並且x的then方法是函式我們就認為他是一個promise
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    // y可能還是一個promise,在去解析直到返回的是一個普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失敗
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { // 說明是一個普通值1
        resolve(x); // 表示成功了
    }
}
複製程式碼

相應的將前面的程式碼進行一些更改

如何實現一個符合promiseA+規範的promise
這時我們就實現了promise的鏈式呼叫。

4. 值的穿透問題


如果在then中什麼都不傳,值會穿透到最後呼叫的時候;

如何實現一個符合promiseA+規範的promise

這時需要在then裡給onFulfilled和onRejected寫一個預設的函式

 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRejected = typeof onRejected === 'function' ? onRejected : function (err) {
        throw err;//這裡需要丟擲錯誤,不能return err,否則會在下一次呼叫成功態
    }
複製程式碼

5. then的非同步實現


規範裡要求,所有的onFulfilled和onRejected都要確保非同步執行

如何實現一個符合promiseA+規範的promise

這裡以resolve為例,寫一個setTimeout():

如何實現一個符合promiseA+規範的promise

6. defer語法糖


在使用promise的過程中,我們都需要先new Promise(),比如說:

function read() {
    let fs = require('fs');
    let promise = new Promise(function(resolve,reject){
        fs.readFile('./1.txt','utf8',function(err,data){
            if(err) reject(err);
            resolve(data);
        })
    });
    return promise
}
複製程式碼

在Promise中,它為我們提供了一個語法糖Promise.defer,用Promise.defer只需這樣寫:

function read() {
    let defer = Promise.defer()
    require('fs').readFile('.//1.txt', 'utf8', function (err, data) {
        if(err) defer.reject(err);
        defer.resolve(data);
    })
    return defer.promise;
}
複製程式碼

為此,再為我們的Promise加一個defer方法:

Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise(function (resolve, reject) {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd
}
複製程式碼

在這裡,我們基本實現了一個比較完整的promise;當然Promise還有許多靜態方法,還有js的非同步發展史,這些可以在下一次進行討論。 完整程式碼:

// promise裡只有一個引數,叫executor(執行器)
function Promise(executor) {
    let self = this;
    self.status = 'pending';//等待態
    self.value = undefined;//預設成功的值
    self.err = undefined;//預設失敗的值
    self.onResolvedCallbacks = []; // 存放then成功的回撥
    self.onRejectedCallbacks = []; // 存放then失敗的回撥
    function resolve(value) {
        if (self.status === 'pending') {
            self.status = 'resolved';
            self.value = value;
            self.onResolvedCallbacks.forEach(function (fn) {
                fn();
            });
        }
    }
    function reject(err) {
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.err = err;
            self.onRejectedCallbacks.forEach(function (fn) {
                fn();
            });
        }
    }
    try {//捕獲時發生異常,直接變成reject態,丟擲錯誤
        executor(resolve, reject);//promise例項建立後,立即執行
    } catch (error) {
        reject(error);  
    }
}
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {//promise2和x不能相同
        return reject(new TypeError('迴圈引用了'))
    }
    let called;// 表示是否呼叫過成功或者失敗
    //這裡對x的型別進行判斷
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {  // 判斷x是不是promise,如果x是物件並且x的then方法是函式我們就認為他是一個promise
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    // y可能還是一個promise,在去解析直到返回的是一個普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失敗
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { // 說明是一個普通值1
        resolve(x); // 表示成功了
    }
}
//在prototype上定義then例項方法
Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRejected = typeof onRejected === 'function' ? onRejected : function (err) {
        throw err;//這裡需要丟擲錯誤,不能return err,否則會在下一次呼叫成功態
    }
    let self = this;
    let promise2; //返回的promise
    if (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRejected(self.err);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })

        })
    }
    // 當呼叫then時可能沒成功 也沒失敗
    if (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            // 此時沒有resolve 也沒有reject
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRejected(self.err);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}
Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise(function (resolve, reject) {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd
}

module.exports = Promise;

複製程式碼

7.Promise測試


npm i -g promises-aplus-tests
promises-aplus-tests Promise.js
複製程式碼

相關文章