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

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

前言

Promise 是異步編程的一種解決方案:
從語法上講,promise是一個對象,從它能夠獲取異步操做的消息;從本意上講,它是承諾,承諾它過一段時間會給你一個結果。
promise有三種狀態: pending(等待態),fulfiled(成功態),rejected(失敗態);狀態一旦改變,就不會再變。創造promise實例後,它會當即執行。

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

在實現以前,能夠先看一下Promise A plus規範jquery

1. 建立promise構造函數


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

// 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
圖片描述編程

這裏便實現了基本功能,前面說過Promise 是異步編程的一種解決方案
咱們加個異步邏輯運行一下:數組

圖片描述

2. Promise異步調用


咱們都知道異步代碼並不會當即執行,這時既不是resolved也不是rejected,而是 pending
在以前的狀態判斷裏面,正好丟了一個pending狀態。OK,這時須要在then裏判斷當status爲pending時,先將onFulfilled, onRejected存入數組裏,當status改變時,再遍歷數組讓裏面的函數依次執行,看代碼。

(1)申明兩個存放onFulfiled,onRejected的數組promise

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);
        })
    }
}

再看剛剛的異步邏輯
圖片描述異步編程

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

3. Promise鏈式調用


(1)規範裏說在同一個promise裏then能夠被屢次調用。測試

圖片描述

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

圖片描述

在then裏新建一個promise2併爲每個狀態包一個Promise

圖片描述

寫到這裏,再來看看規範,規範裏說道
(1)x多是一個promise;

圖片描述

(2)多是一個對象或者方法;

圖片描述

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

圖片描述

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

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


因而引入一個處理方法resolvePromise(promise2, x, resolve, reject);
這裏須要注意一下,有些人寫的promise可能會既調用成功,又調用失敗,若是兩個都調用先調用誰另外一個就忽略掉。
因而增長一個判斷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); // 表示成功了
    }
}

相應的將前面的代碼進行一些更改
圖片描述

4. 值的穿透問題


若是在then中什麼都不傳,值會穿透到最後調用的時候;
圖片描述

這時須要在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都要確保異步執行

圖片描述

這裏以resolve爲例,寫一個setTimeout():
圖片描述

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
相關文章
相關標籤/搜索