前言

  1. 上篇博客写着写着没动力,然后就拖了一个月。
  2. 现在打算在一周内完成。
  3. 这篇讲Promiseco的原理+实现。

一、Promise 的原理

Promise 的规范有很多,其中ECMAScript 6采用的是Promises/A+. 想要了解更多最好仔细读完Promises/A+,顺便说下Promise是依赖于异步实现。

JavaScript 中的异步队列

而在JavaScript中有两种异步宏任务macro-task和微任务micro-task.

在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。

常见的异步代码实现

  • macro-task: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
  • micro-task: process.nextTick, Promises(原生 Promise), Object.observe(api 已废弃), MutationObserver

以上的知识摘查于Promises/A+ 顺便说下一个前段时间看到的一个js面试题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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实例的队列。

二、Promise 的简易实现

  • Promise Api

下面是Promise的所有开放 api 这里为了区分与原生的所以类名叫Appoint。 顺便为了学习typescript

1
2
3
4
5
6
7
8
9
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* 如果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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* 使用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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

1
2
3
4
5
6
7
8
9
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* 传入一个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);
            }
        });
    }
}

三、co 原理

不使用 co 的话不停的 then,和 callback 明显会很难受。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function callback (null, name) {
    console.log(name)
}
new Promise(function(resolve) {
        resolve('<h1>test</h1>')
}).then(html => {
    setTimeout(function(){
        callback('test' + html)
    }, 100)
})

改用 co 异步代码感觉和写同步代码一样。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 了,直接看执行效果吧:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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中从而达到效果。

四、co 的实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;
}

转换方法这个懒得说了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
    });
}

还有一些判断工具函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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;
}

五、资料

  1. 项目源代码
  2. 深入 Promise(一)——Promise 实现详解
  3. 英文版 Promise/A+
  4. 中文版 Promise/A+

六、后记

  1. 这次逼着自己写 3 天就写完了,果然就是懒。
  2. 接下来写一个系列文章preact的源码解析与实现。
  3. 尽量一周出一篇?看看情况吧。