Promise和co的原理實現

前言

  1. 上篇博客寫着寫着沒動力,而後就拖了一個月。javascript

  2. 如今打算在一週內完成。html

  3. 這篇講Promiseco的原理+實現。java

1、Promise的原理

Promise的規範有不少,其中ECMAScript 6採用的是Promises/A+.
想要了解更多最好仔細讀完Promises/A+,順便說下Promise是依賴於異步實現。node

JavaScript中的異步隊列

而在JavaScript中有兩種異步宏任務macro-task和微任務micro-task.react

在掛起任務時,JS 引擎會將全部任務按照類別分到這兩個隊列中,首先在 macrotask 的隊列(這個隊列也被叫作 task queue)中取出第一個任務,執行完畢後取出 microtask 隊列中的全部任務順序執行;以後再取 macrotask 任務,周而復始,直至兩個隊列的任務都取完。git

常見的異步代碼實現github

  • macro-task: script(總體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering面試

  • micro-task: process.nextTick, Promises(原生 Promise), Object.observe(api已廢棄), MutationObservertypescript

以上的知識摘查於Promises/A+
順便說下一個前段時間看到的一個js面試題api

setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for( var i=0 ; i<10000 ; i++ ) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);

在node v8.13使用原生的Promise是: "2 3 5 4 1"。
可是若是使用bluebird的第三方Promise就是: "2 3 5 1 4"。
這個緣由是由於bluebird在這個環境下優先使用setImmediate代碼
而後再看上面的代碼執行順序.

  1. 第一次總體代碼進入macro-taskmicro-task爲空。

  2. macro-task執行總體代碼,setTimeout加入下一次的macro-taskPromise執行打出2 3then加入micro-task, 最後打出5

  3. micro-task執行then被執行因此打出4

  4. 從新執行macro-task因此打出1

可是在bluebird裏的then使用setImmediate因此上面的步驟會變成:

  • 步驟2thensetTimeout後加入macro-task

  • 步驟3會由於micro-task爲空跳過。

  • 步驟4 執行setTimeoutthen打出1 4

Promise執行流程

  1. new Promise(func:(resolve, reject)=> void 0), 這裏的func方法被同步執行。

  2. Promise 會有三種狀態PENDING(執行)FULFILLED(執行成功),REJECTED(執行失敗)

  3. resolvereject均未調用且未發生異常時狀態爲PENDING

  4. resolve調用爲FULFILLED,reject調用或者發生異常爲REJECTED

  5. 在給Promise實例調用then(callFulfilled, callRejected)來設置回調,狀態不爲PENDING時會根據狀態調用callFulfilledcallRejected

  6. then須要返回一個新的Promise實例.

  7. 狀態爲PENDING則會把callFulfilledcallRejected放入當前Promise實例的回調隊列中,隊列還會存儲新的Promise實例。

  8. 在狀態改變爲FULFILLEDREJECTED時會回調當前Promise實例的隊列。

2、Promise的簡易實現

  • Promise Api

下面是Promise的全部開放api這裏爲了區分與原生的因此類名叫Appoint
順便爲了學習typescript

class Appoint {
    public constructor(resolver: Function){};
    public then(onFulfilled, onRejected): Appoint {};
    public catch(onRejected): Appoint {};
    public static resolve(value): Appoint {};
    public static reject(error): Appoint {};
    public static all(iterable: Appoint[]): Appoint {};
    public static race(iterable): Appoint {};
}

Appoint

function INTERNAL() {}
enum AppointState {
    PENDING,
    FULFILLED,
    REJECTED,
}
class Appoint {
    public handled: boolean;
    public value: any;
    public queue: QueueItem[];
    private state: AppointState;
    public constructor(resolver: Function){
        if (!isFunction(resolver)) {
            throw new TypeError("resolver must be a function");
        }
        // 設置當前實例狀態
        this.state = AppointState.PENDING;
        this.value = void 0;
        // 初始化回調隊列
        this.queue = [];
        // true表明沒有設置then
        this.handled = true;
        if (resolver !== INTERNAL) {
            // 安全執行傳入的函數
            safelyResolveThen(this, resolver);
        }
    }
    // state 的getset
    public setState(state: AppointState) {
        if (this.state === AppointState.PENDING && this.state !== state) {
            this.state = state;
        }
    }
    public getState(): AppointState {
        return this.state;
    }
}

safelyResolveThen

function safelyResolveThen(self: Appoint, then: (arg: any) => any) {
    let called: boolean = false;
    try {
        then(function resolvePromise(value: any) {
            if (called) {
                return;
            }
            // 保證doResolve,doReject只執行一次
            called = true;
            // 改變當前狀態以及調用回調隊列
            doResolve(self, value);
        }, function rejectPromise(error: Error) {
            if (called) {
                return;
            }
            // 同上
            called = true;
            doReject(self, error);
        });
    } catch (error) {
        // 特別捕捉錯誤
        if (called) {
            return;
        }
        called = true;
        doReject(self, error);
    }
}

doResolve, doReject

/**
* 若是value不是一個Promise,對Promise調用回調隊列。
* 若是是就等待這個Promise回調
*/
function doResolve(self: Appoint, value: any) {
    try {
        // 判斷是否爲Promise
        const then = getThen(value);
        if (then) {
            //
            safelyResolveThen(self, then);
        } else {
            // 改變狀態
            self.setState(AppointState.FULFILLED);
            self.value = value;
            // 調用回調隊列
            self.queue.forEach((queueItem) => {
                queueItem.callFulfilled(value);
            });
        }
        return self;
    } catch (error) {
        return doReject(self, error);
    }
}
/**
* 調用回調隊列
*/
function doReject(self: Appoint, error: Error) {
    // 改變狀態
    self.setState(AppointState.REJECTED);
    self.value = error;
    if (self.handled) {
        // 未設置then回調
        asap(() => {
            // 建立一個異步任務保證代碼都執行了再判斷
            if (self.handled) {
                if (typeof process !== "undefined") {
                    // node 環境下觸發unhandledRejection事件
                    process.emit("unhandledRejection", error, self);
                } else {
                    // 瀏覽器環境直接打印便可
                    console.error(error);
                }
            }
        });
    }
    self.queue.forEach((queueItem) => {
        queueItem.callRejected(error);
    });
    return self;
}
/**
* 判斷是否爲Object且有then屬性的方法,
* 有返回這個方法的綁定this
* 這種判斷方式會發生若是
* resolve({ then: () => {} })的話就會丟失下次的then
* 原生的Promise也是相同
*/
function getThen(obj: any): Function {
    const then = obj && obj.then;
    if (obj && (isObject(obj) || isFunction(obj)) && isFunction(then){
        return then.bind(obj);
    }
    return null;
}

Appoint().then, Appoint().catch

/**
* 使用micro-task的異步方案來執行方法
*/
function asap(callback) {
    if (typeof process !== "undefined") {
        process.nextTick(callback);
    } else {
        const BrowserMutationObserver = window.MutationObserver || window.WebKitMutationObserver
        let iterations = 0;
        const observer = new BrowserMutationObserver(callback);
        const node: any = document.createTextNode("");
        observer.observe(node, { characterData: true });
        node.data = (iterations = ++iterations % 2);
    }
}
/**
* 異步執行then,catch
*/
function unwrap(promise: Appoint, func: Function, value: any): void {
    asap(() => {
        let returnValue;
        try {
            // 執行then,catch回調得到返回值
            returnValue = func(value);
        } catch (error) {
            // 發生異常直接觸發該promise的Reject
            return doReject(promise, error);
        }
        if (returnValue === promise) {
            // 執行then,catch回調返回值不能爲promise本身
            doReject(promise, new TypeError("Cannot resolve promise with itself"));
        } else {
            // then,catch回調成功,直接觸發該promise的Resolve
            doResolve(promise, returnValue);
        }
    });
}

public then<U>(
    onFulfilled?: (value?: any) => U,
    onRejected?: (error?: any) => U,
): Appoint {
    // 直接無視來作到值穿透
    if (!isFunction(onFulfilled) &&
        this.state === AppointState.FULFILLED ||
        !isFunction(onRejected) &&
        this.state === AppointState.REJECTED
    ) {
            return this;
    }
    // 新建一個空的
    const promise = new Appoint(INTERNAL);
    // 當前實例已經被設置then
    if (this.handled) {
        this.handled = false;
    }
    if (this.getState() !== AppointState.PENDING) {
        // 當前實例已經結束運行直接根據狀態獲取要回調的方法
        const resolver = this.getState() === AppointState.FULFILLED ? onFulfilled : onRejected;
        // 異步執行resolver,若是成功會觸發新實例的then,catch
        unwrap(promise, resolver, this.value);
    } else {
        // 若是Promise的任務還在繼續就直接把生成一個QueueItem
        // 並設置好新的Promise實例
        this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
    }
    // 返回新生成的
    return promise;
}
public catch<U>(onRejected: (error?: any) => U): Appoint {
    return this.then(null, onRejected);
}

QueueItem

export class QueueItem {
    // 每次then|catch生成的新實例
    public promise: Appoint;
    // then回調
    public callFulfilled: Function;
    // catch回調
    public callRejected: Function;
    constructor(promise: Appoint, onFulfilled?: Function, onRejected?: Function {
        this.promise = promise;
        if (isFunction(onFulfilled)) {
            this.callFulfilled = function callFulfilled(value: any) {
                // 異步執行callFulfilled,後觸發新實例的then,catch
                unwrap(this.promise, onFulfilled, value);
            };
        } else {
            this.callFulfilled = function callFulfilled(value: any) {
                // 沒有設置callFulfilled的話直接觸發新實例的callFulfilled
                /*
                例以下面這種代碼一次catch可是沒有then而下面代碼中的then是catch返回的新實例
                因此須要直接
                new Promise(() => {
                })
                .catch(() => {})
                .then()
                */
                doResolve(this.promise, value);
            };
        }
        if (isFunction(onRejected)) {
            this.callRejected = function callRejected(error: Error) {
                // 異步執行callRejected,後會觸發新實例的then,catch
                unwrap(this.promise, onRejected, error);
            };
        } else {
            this.callRejected = function callRejected(error: Error) {
                // 沒有設置callRejected的話直接觸發新實例的callRejected
                doReject(this.promise, error);
            };
        }
    }
}

utils

export function isFunction(func: any): boolean {
    return typeof func === "function";
}
export function isObject(obj: any): boolean {
    return typeof obj === "object";
}
export function isArray(arr: any): boolean {
    return Object.prototype.toString.call(arr) === "[object Array]";
}

Appoint.resolve, Appoint.reject

public static resolve(value: any): Appoint {
    if (value instanceof Appoint) {
        return value;
    }
    return doResolve(new Appoint(INTERNAL), value);
}
public static reject(error: any): Appoint {
    if (error instanceof Appoint) {
        return error;
    }
    return doReject(new Appoint(INTERNAL), error);
}

Appoint.all, Appoint.race

/**
* 傳入一個Promise數組生成新的Promise全部Promise執行完後回調
*/
public static all(iterable: Appoint[]): Appoint {
    const self = this;
    if (!isArray(iterable)) {
        return this.reject(new TypeError("must be an array"));
    }
    const len = iterable.length;
    let called = false;
    if (!len) {
        return this.resolve([]);
    }
    const values = new Array(len);
    let i: number = -1;
    const promise = new Appoint(INTERNAL);
    while (++i < len) {
        allResolver(iterable[i], i);
    }
    return promise;
    function allResolver(value: Appoint, index: number) {
        self.resolve(value).then(resolveFromAll, (error: Error) => {
            if (!called) {
                called = true;
                doReject(promise, error);
            }
        });
        function resolveFromAll(outValue: any) {
            values[index] = outValue;
            if (index === len - 1 && !called) {
                called = true;
                doResolve(promise, values);
            }
        }
    }
}
/**
* 與all相似可是,只要一個Promise回調的就回調
*/
public static race(iterable: Appoint[]): Appoint {
    const self = this;
    if (!isArray(iterable)) {
        return this.reject(new TypeError("must be an array"));
    }
    const len = iterable.length;
    let called = false;
    if (!len) {
        return this.resolve([]);
    }
    const values = new Array(len);
    let i: number = -1;
    const promise = new self(INTERNAL);
    while (++i < len) {
        resolver(iterable[i]);
    }
    return promise;
    function resolver(value: Appoint) {
        self.resolve(value).then((response: any) => {
            if (!called) {
                called = true;
                doResolve(promise, response);
            }
        }, (error: Error) => {
            if (!called) {
                called = true;
                doReject(promise, error);
            }
        });
    }
}

3、co 原理

不使用co的話不停的then,和callback明顯會很難受。

function callback (null, name) {
    console.log(name)
}
new Promise(function(resolve) {
        resolve('<h1>test</h1>')
}).then(html => {
    setTimeout(function(){
        callback('test' + html)
    }, 100)
})

改用co異步代碼感受和寫同步代碼同樣。

const co = require("co")
co(function *test()  {
    const html = yield new Promise(function(resolve) {
        resolve('<h1>test</h1>')
    })
    console.log('--------')
    const name = yield function (callback) {
        setTimeout(function(){
            callback(null, 'test' + html)
        }, 100)
    }
    return name
}).then(console.log)

這裏不得不說下Generator了,直接看執行效果吧:

function *gen() {
    const a = yield 1
    console.log('a: ', a)
    const b = yield 2
    console.log('b: ', b)
    return 3
}
const test = gen()
test.next() // Object { value: 1, done: false }
test.next(4) // a: 4\n Object { value: 2, done: false }
test.next(5) // b: 5\n Object { value: 3, done: true }

很明顯除了第一次next的參數都會賦值到上一次的yield的左邊變量。
最後一次的next返回的valuereturn的值,其它都是yield右邊的變量。
co就是經過不停的next獲取到支持的異步對象回調後把值放到下次的next中從而達到效果。

4、co 的實現

const slice = Array.prototype.slice;
const co: any = function co_(gen) {
    const ctx = this;
    const args = slice.call(arguments, 1);
    return new Promise(function _(resolve, reject) {
        // 把傳入的方法執行一下並存下返回值
        if (typeof gen === "function") {
            gen = gen.apply(ctx, args);
        }
        // 1. 傳入的是一個方法經過上面的執行得到的返回值,
        // 若是不是一個有next方法的對象直接resolve出去
        // 2. 傳入的不是一個方法且不是一個next方法的對象直接resolve出去
        if (!gen || typeof gen.next !== "function") {
            return resolve(gen);
        }
        // 執行,第一次next不須要值
        onFulfilled();
        /**
            * @param {Mixed} res
            * @return {null}
            */
        function onFulfilled(res?: any) {
            let ret;
            try {
                // 獲取next方法得到的對象,並把上一次的數據傳遞過去
                ret = gen.next(res);
            } catch (e) {
                // generator 獲取下一個yield值發生異常
                return reject(e);
            }
            // 處理yield的值把它轉換成promise並執行
            next(ret);
            return null;
        }
        /**
            * @param {Error} err
            * @return {undefined}
            */
        function onRejected(err) {
            let ret;
            try {
                // 把錯誤拋到generator裏,而且接收下次的yield
                ret = gen.throw(err);
            } catch (e) {
                // generator 獲取下一個yield值發生異常
                return reject(e);
            }
            // 處理yield的值
            next(ret);
        }
        function next(ret) {
            // generator執行完並把返回值resolve出去
            if (ret.done) {
                return resolve(ret.value);
            }
            // 把value轉換成Promise
            const value = toPromise(ctx, ret.value);
            if (value && isPromise(value)) {
                // 等待Promise執行
                return value.then(onFulfilled, onRejected);
            }
            // yield的值不支持
            return onRejected(new TypeError("You may only yield a function, promise,"
                + " generator, array, or object, "
                + 'but the following object was passed: "' + String(ret.value) + '"'));
        }
    });
};

toPromise

function toPromise(ctx: any, obj: any) {
    if (!obj) { return obj; }
    if (isPromise(obj)) { return obj; }
    // 判斷是 Generator 對象|方法 直接經過 co 轉換爲Promise
    if (isGeneratorFunction(obj) || isGenerator(obj)) {
        return co.call(ctx, obj);
    }
    // 判斷是個回調方法
    if ("function" === typeof obj) {
        return thunkToPromise(ctx, obj);
    }
    // 判斷是個數組
    if (Array.isArray(obj)) {
        return arrayToPromise(ctx, obj);
    }
    // 根據對象屬性把全部屬性轉爲一個Promise
    if (isObject(obj)) {
        return objectToPromise(ctx, obj);
    }
    // 基礎數據類 1 , true
    return obj;
}

轉換方法這個懶得說了

function thunkToPromise(ctx, fn) {
    return new Promise(function _p(resolve, reject) {
        fn.call(ctx, function _(err, res) {
            if (err) { return reject(err); }
            if (arguments.length > 2) {
                res = slice.call(arguments, 1);
            }
            resolve(res);
        });
    });
}

function arrayToPromise(ctx, obj: any[]) {
    return Promise.all(obj.map((item) => toPromise(ctx, item)));
}

function objectToPromise(ctx, obj) {
    const results = {};
    const keys = Object.keys(obj);
    const promises = [];
    for (let i = 0, len = keys.length; i < len; i++) {
        const key = keys[i];
        const val = obj[key];
        const promise = toPromise(ctx, val);
        if (promise && isPromise(promise)) {
            promises.push(promise.then(function _(res) {
                results[key] = res;
            }));
        } else {
            results[key] = val;
        }
    }
    return Promise.all(promises).then(function _() {
        return results;
    });
}

還有一些判斷工具函數

function isPromise(obj: { then: Function) {
    return "function" === typeof obj.then;
}
function isGenerator(obj) {
    return "function" === typeof obj.next &&
        "function" === typeof obj.throw;
}
function isGeneratorFunction(obj) {
    const constructor = obj.constructor;
    if (!constructor) { return false; }
    if ("GeneratorFunction" === constructor.name ||
        "GeneratorFunction" === constructor.displayName) {
        return true;
    }
    return isGenerator(constructor.prototype);
}
function isObject(val) {
    return Object === val.constructor;
}

5、資料

  1. 項目源代碼

  2. 深刻 Promise(一)——Promise 實現詳解

  3. 英文版Promise/A+

  4. 中文版Promise/A+

6、後記

  1. 此次逼着本身寫3天就寫完了,果真就是懶。

  2. 接下來寫一個系列文章preact的源碼解析與實現。

  3. 儘可能一週出一篇?看看狀況吧。

原文地址

相關文章
相關標籤/搜索