手写Promise

根据功能一步一步实现一个Promise

Promise的特性

先看下原生Promise的简单使用

1
2
3
4
5
6
7
8
9
10
11
12
const p = new Promise((resolve, reject) => {
resolve('success')
reject('failure')
})

p.then(value => {
console.log('resolve ', value);
}, err => {
console.log('reject ', err);
})

// 输出: resolve success

要实现一个Promise我们首先需要了解它有哪些特性

  1. Promise是一个类, 在执行这个类的时候会传入一个执行器(handle), 这个执行器会立即执行.
  2. Promise会有三种状态
    • Pending 等待
    • Fulfilled 完成
    • Rejected 失败
  3. 状态只能由 Pending --> Fulfilled 或者Pending --> Rejected, 且一旦发生改变就不可二次修改.
  4. Promise中使用resolve和reject两个函数来改变状态
  5. then方法内部做的就是判断状态
    • 如果状态是成功, 调用成功回调函数
    • 如果状态是失败, 调用失败回调函数

最简单的Promise

  1. 新建类

    要实现上面的功能, 首先我们需要创建个类, 并且传入执行函数(handle)

    handle函数会有两个参数分别是resolve和reject

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 新建CPromise.js

    // 新建CPromise类
    class CPromise {
    constructor(handle) {
    // handle 是一个执行器, 进入会立即执行
    handle(this.resolve, this.reject);
    }
    // 这边的resolve和reject是个箭头函数, this指向当前实例对象本身
    resolve = () => {}
    reject = () => {}
    }
  2. 状态与结果管理

    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
    // CPromise.js

    // 定义三个常量表示状态
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'

    class CPromise {
    // 储存状态的变量, 赋初始值pending
    status = PENDING
    // 存放成功的值
    value = null
    // 存放失败原因
    reason = null
    // 更改成功后的状态
    resolve = (value) => {
    // 只有等待状态, 才执行状态修改
    if (this.status === PENDING) {
    // 修改状态为成功
    this.status = FULFILLED
    // 保存成功之后的值
    this.value = value
    }
    }
    // 更改失败后的状态
    reject = (reason) => {
    // 只有等待状态, 才执行状态修改
    if (this.status === PENDING) {
    // 修改状态为失败
    this.status = REJECTED
    // 保存失败后的原因
    this.reason = reason
    }
    }
    }
  3. 简单实现then方法

    接下来就是then函数了, then函数可以传入两个参数, 分别是失败和成功的回调函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // CPromise.js

    class CPromise {
    then(onFulfilled, onRejected) {
    // 判断状态
    if (this.status === FULFILLED) {
    // 调用成功回调, 并将值返回
    onFulfilled(this.value)
    } else if (this.status === REJECTED) {
    // 调用失败回调, 并将原因返回
    onRejected(this.reason)
    }
    }
    }

    将上面例子中的原生promise替换成CPromise, 执行代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const p = new CPromise((resolve, reject) => {
    resolve("success");
    reject("failure");
    });

    p.then(value => {
    console.log('resolve ', value);
    }, err => {
    console.log('reject ', err);
    })

    // 输出: resolve success

    输出结果正确

在Promise类中加入异步逻辑

上面的例子还没有经过异步处理, 一旦加入异步逻辑, 代码执行就会异常

1
2
3
4
5
6
7
8
9
10
11
const p = new CPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000);
});

p.then(value => {
console.log('resolve ', value);
})

// 没有输出信息!!!

分析原因: 主线程程序会立即执行, 而setTimeout是异步代码, 示例中的then函数会立即执行, resolve函数会在1s后才执行, 所以执行then函数时的status状态仍然是pending.then的参数函数不会执行

  1. 缓存成功与失败回调函数

    1
    2
    3
    4
    5
    6
    7
    8
    // CPromise.js

    class CPromise {
    // 存储成功回调函数
    onFulfilledCallback = null
    // 存储失败回调函数
    onRejectedCallback = null
    }
  1. then函数中添加对pending状态的处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // CPromise.js

    class CPromise{
    then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
    onFulfilled(this.value)
    } else if (this.status === REJECTED) {
    onRejected(this.value)
    } else if (this.status === PENDING) {
    // 此时状态还未变化, 先讲回调函数缓存起来, 等状态变化时再执行
    this.onFulfilledCallback = onFulfilled
    this.onRejectedCallback = onRejected
    }
    }
    }
  2. 在resolve及reject中调用回调函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // CPromise.js
    class CPromise{
    resolve = (value) => {
    if (this.status === PENDING) {
    this.status = FULFILLED
    this.value = value
    // 判断回调是否存在, 如果存在就调用
    this.onFulfilledCallback && this.onFulfilledCallback(value)
    }
    }
    reject = (reason) => {
    if (this.status === PENDING) {
    this.status = REJECTED;
    this.reason = reason;
    // 判断回调是否存在, 如果存在就调用
    this.onRejectedCallback && this.onRejectedCallback(reason);
    }
    }
    }
  3. 再执行上面的例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const p = new CPromise((resolve, reject) => {
    setTimeout(() => {
    resolve('success')
    }, 1000);
    });

    p.then(value => {
    console.log('resolve ', value);
    })

    // 输出: resolve success

    输出结果正确

当then函数被多次调用

Promise的then函数是可以被多次调用的, 如果同时有三个then函数, promise被执行resolve时, 三个then都会被调用

  1. 改用数组来存储回调函数

    1
    2
    3
    4
    5
    6
    7
    8
    class CPromise{
    // 存储成功回调函数
    // onFulfilledCallback = null
    onFulfilledCallbacks = [];
    // 存储失败回调函数
    // onRejectedCallback = null
    onRejectedCallbacks = []
    }
  2. 将回调函数存放到数组中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class CPromise{
    then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
    onFulfilled(this.value);
    } else if (this.status === REJECTED) {
    onRejected(this.value);
    } else if (this.status === PENDING) {
    // 此时状态还未变化, 先讲回调函数缓存起来, 等状态变化时再执行
    // this.onFulfilledCallback = onFulfilled;
    this.onFulfilledCallbacks.push(onFulfilled)
    // this.onRejectedCallback = onRejected;
    this.onRejectedCallbacks.push(onRejected)
    }
    }
    }
  3. 循环调用成功和失败的回调函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class CPromise{
    resolve = (value) => {
    if (this.status === PENDING) {
    this.status = FULFILLED;
    this.value = value;
    // 判断回调是否存在, 如果存在就调用
    // this.onFulfilledCallback && this.onFulfilledCallback(value);
    while (this.onFulfilledCallbacks.length) {
    this.onFulfilledCallbacks.shift()(value)
    }
    }
    };
    reject = (reason) => {
    if (this.status === PENDING) {
    this.status = REJECTED;
    this.reason = reason;
    // 判断回调是否存在, 如果存在就调用
    // this.onRejectedCallback && this.onRejectedCallback(reason);
    while (this.onRejectedCallbacks.length) {
    this.onRejectedCallbacks.shift()(reason);
    }
    }
    };
    }
  4. 执行代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    const p = new CPromise((resolve, reject) => {
    setTimeout(() => {
    resolve('success')
    }, 1000);
    });

    p.then(value => {
    console.log('resolve 1 ', value);
    })
    p.then((value) => {
    console.log("resolve 2 ", value);
    });
    p.then((value) => {
    console.log("resolve 3 ", value);
    });

    /**
    * 输出:
    * resolve 1 success
    * resolve 2 success
    * resolve 3 success
    */

实现then方法的链式调用

原生的promise是可以链式调用的, 并且then方法里面return一个返回值作为下一个then方法的参数, 如果return一个Promise对象, 就判断它的状态

而看我们现在的CPromise, 不具备链式调用功能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// test.js
const p = new CPromise((resolve, reject) => {
// setTimeout(() => {
resolve("success");
// }, 1000);
});

function other() {
return new CPromise((resolve, reject) => {
resolve('other')
})
}

p.then((value) => {
console.log("resolve 1 ", value);
return other()
}).then((value) => {
console.log("resolve 2 ", value);
});

/* 输出:
* }).then((value) => {
*/ TypeError: Cannot read property 'then' of undefined

修改then方法, 返回CPromise. (为了方便描述, 下面我们将例子中链式调用的then函数分别称为then1和then2)

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
class CPromise{
then(onFulfilled, onRejected) {
// 为了链式调用, 直接返回一个CPromise
const promise2 = new CPromise((resolve, reject) => {
// 这里的内容也是立即执行
if (this.status === FULFILLED) {
// 获取成功回调函数的执行结果
const x = onFulfilled(this.value);
// 传入resolvePromise集中处理
resolvePromise(x, resolve, reject)
} else if (this.status === REJECTED) {
onRejected(this.value);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
})
return promise2
}
}

function resolvePromise(x, resolve, reject) {
if (x instanceof CPromise) {
// resolve和reject是then方法返回的promise的执行函数的参数
// 因此想要链式调用中第二个then方法的onFulfilled被执行, 首先要执行resolve方法
// 如果x是CPromise类型, 将resolve对应给x, 一旦x执行了改变了状态, x.then的onFulfilled就会被执行, resolve也会被执行, 对应的链式调用中then2方法也会被执行
x.then(resolve, reject)
} else {
resolve(x)
}
}

执行上面的例子, 结果符合预期

1
2
resolve 1  success
resolve 2 other

但是目前只支持同步方法的链式调用, 一旦加入异步逻辑还是会出错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const p = new CPromise((resolve, reject) => {
setTimeout(() => {
resolve("success");
}, 1000);
});

function other() {
return new CPromise((resolve, reject) => {
resolve('other')
})
}

p.then((value) => {
console.log("resolve 1 ", value);
return other()
}).then((value) => {
console.log("resolve 2 ", value);
});

// 输出: resolve 1 success

可以发现then2的onFulfilled未执行

分析原因: 异步时进入then1方法时状态为pending, onFulfilledCallbacks会缓存当前onFulfilled, 并在resolve方法被调用时再触发, 此时触发我们并没有对then1返回值进行处理.

解决方案: 将对返回值的处理封装成函数dealResolve, 异步时将dealResolve添加到onFulfilledCallbacks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class CPromise{
then(onFulfilled, onRejected) {
// 为了链式调用, 直接返回一个CPromise
const promise2 = new CPromise((resolve, reject) => {
let dealResolve = _value => {
// 获取成功回调函数的执行结果
const x = onFulfilled(_value);
// 传入resolvePromise集中处理
resolvePromise(x, resolve, reject)
}
// 这里的内容也是立即执行
if (this.status === FULFILLED) {
dealResolve(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(dealResolve);
this.onRejectedCallbacks.push(onRejected);
}
})
return promise2
}
}

再试试上面的例子

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
const p = new CPromise((resolve, reject) => {
setTimeout(() => {
resolve("success");
}, 1000);
});

function other() {
return new CPromise((resolve, reject) => {
setTimeout(() => {
resolve("other");
}, 1000);
})
}

p.then((value) => {
console.log("resolve 1 ", value);
return other()
}).then((value) => {
console.log("resolve 2 ", value);
});

/* 输出:
* resolve 1 success
* resolve 2 other
*/

结果符合预期

then方法中返回自己

如果then方法返回的是自己的Promise对象, 则会发生循环调用, 这时程序会报错

看看原生Promise会咋样

1
2
3
4
5
6
7
8
9
10
const p = new Promise((resolve, reject) => {
resolve("success");
});

const p1 = p.then(value => {
console.log(value);
return p1
})

// 输出: UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise

我们也在自己的CPromise中实现一下该功能

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
class CPromise{
then(onFulfilled, onRejected) {
const promise2 = new CPromise((resolve, reject) => {
let dealResolve = _value => {
const x = onFulfilled(_value);
// 传入自身
resolvePromise(promise2, x, resolve, reject);
}
if (this.status === FULFILLED) {
dealResolve(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(dealResolve);
this.onRejectedCallbacks.push(onRejected);
}
})
return promise2
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 判断返回值与自身是否是一个对象
if (promise2 === x) {
// 抛出类型错误异常
throw new TypeError("Chaining cycle detected for promise");
}
if (x instanceof CPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
}

运行上面的例子

1
2
3
4
5
6
7
8
9
10
const p = new CPromise((resolve, reject) => {
resolve("success");
});

const p1 = p.then(value => {
console.log(value);
return p1
})

// 输出: ReferenceError: Cannot access 'p1' before initialization

执行后发现报错了, p1还未初始化完成不能使用. 我们需要创建一个异步函数去等待p2完成初始化.

考虑到promise本身是个微任务, 所以我们选取异步函数时也要选取一个微任务来完成我们的预期. 这里我们选用queueMicrotask.(最后会有宏任务与微任务的几种创建方式介绍)

添加微任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CPromise {
then(onFulfilled, onRejected) {
const promise2 = new CPromise((resolve, reject) => {
let dealResolve = _value => {
// 创建微任务, 等待promise2初始化完成
queueMicrotask(() => {
const x = onFulfilled(_value);
resolvePromise(promise2, x, resolve, reject);
})
}
if (this.status === FULFILLED) {
dealResolve(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(dealResolve);
this.onRejectedCallbacks.push(onRejected);
}
})
return promise2
}
}

再次运行代码 符合预期

捕获错误及then链式调用其他状态处理

首先是执行器中的异常

1
2
3
4
5
6
7
8
9
class CPromise{
constructor(handle) {
try {
handle(this.resolve, this.reject);
} catch (error) {
this.reject(error)
}
}
}

输出例子

1
2
3
4
5
6
7
8
9
10
const p = new CPromise((resolve, reject) => {
throw new Error('异常')
});
p.then(value => {
console.log('resolve 1 ', value);
return other();
}, err => {
console.log(err);
})
// 输出: Error: 异常

then执行时的错误捕获

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
class CPromise {
then(onFulfilled, onRejected) {
const promise2 = new CPromise((resolve, reject) => {
let dealResolve = (_value) => {
queueMicrotask(() => {
// 未onFulfilled加入异常捕获
try {
const x = onFulfilled(_value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
onRejected(error);
}
});
};
if (this.status === FULFILLED) {
dealResolve(this.value);
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(dealResolve);
this.onRejectedCallbacks.push(onRejected);
}
});
return promise2;
}
}

输出例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const p = new CPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000);
});

p.then(value => {
console.log('resolve 1 ', value);
throw new Error('error1')
}, err => {
console.log(err);
})

/* 输出:
* resolve 1 success
* Error: error1
*/

补充对rejected的处理

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
class CPromise {
then(onFulfilled, onRejected) {
const promise2 = new CPromise((resolve, reject) => {
let dealResolve = (_value) => {
queueMicrotask(() => {
try {
const x = onFulfilled(_value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
onRejected(error);
}
});
};
let dealRejected = (_reason) => {
// 创建微任务, 等待promise2初始化完成
queueMicrotask(() => {
try {
const x = onRejected(_reason)
rejectPromise(promise2, x, resolve, reject)
} catch (error) {
onRejected(error)
}
})
}
// 这里的内容也是立即执行
if (this.status === FULFILLED) {
dealResolve(this.value);
} else if (this.status === REJECTED) {
dealRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(dealResolve);
this.onRejectedCallbacks.push(dealRejected);
}
});
return promise2;
}
}

function rejectPromise(promise2, x, resolve, reject) {
// 判断返回值与自身是否是一个对象
if (promise2 === x) {
throw new TypeError("Chaining cycle detected for promise");
}
if (x instanceof CPromise) {
x.then(resolve, reject);
} else {
reject(x);
}
}

then中的参数变为可选

// todo

实现resolve与reject的静态调用

// todo

CPromise完整代码

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
75
76
77
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class CPromise {
status = PENDING;
value = null;
reason = null;
onFulfilledCallbacks = [];
onRejectedCallbacks = [];

constructor(handle) {
handle(this.resolve, this.reject);
}
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(value);
}
}
};
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason);
}
}
};

then(onFulfilled, onRejected) {
const promise2 = new CPromise((resolve, reject) => {
let dealResolve = _value => {
// 创建微任务, 等待promise2初始化完成
queueMicrotask(() => {
const x = onFulfilled(_value);
resolvePromise(promise2, x, resolve, reject);
})
}
// 这里的内容也是立即执行
if (this.status === FULFILLED) {
dealResolve(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(dealResolve);
this.onRejectedCallbacks.push(onRejected);
}
})
return promise2
}
}

function resolvePromise(promise2, x, resolve, reject) {
// 判断返回值与自身是否是一个对象
if (promise2 === x) {
throw new TypeError("Chaining cycle detected for promise");
}
if (x instanceof CPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
}

try {
if (window) {
window['CPromise'] = CPromise
} else {
module.exports = CPromise;
}
} catch (error) {
module.exports = CPromise;
}