从零开始实现一个符合A+规范的Promise

从零开始实现一个符合A+规范的Promise

Promise是什么,解决了什么问题?

Promise是一种用于异步编程的JavaScript对象。主要用于处理异步操作 的结果。

异步导致的问题回调地狱(让代码难以阅读)、错误处理(无法统一处理错误)、多个异步操作(同步结果困难)

  • Promise可以使用.then() 方法链式处理异步逻辑
  • Promise可以使用catch().方法处理异步操作失败的情况
  • Promise提供.all().race()方法支持处理多个Promise对象的结果

开始实现一个符合A+规范的Promise

最基础的版本

我们平常使用Promise时的用法通常是这样的

1
2
3
4
5
6
7
8
9
10
11
12
const p1 = new Promise((resolve,reject)=> {
// todo...
resolve('成功')
// reject('失败')
// throw new Error('error')
})

p1.then(value => {
console.log('成功',value)
},error => {
console.log('失败',reason)
})

从这种使用方法结合平常的使用我们可以知道Promise具有以下特点

  • Promise是一个类,使用的时候需要new Promise来产生一个promise实例
  • 构造函数中需要传递一个参数 executor , executor是立即执行的
  • executor参数中有两个参数 resolve(value) reject(reason)
  • 调用resolve会让promise 变为成功reject会变成失败
  • 有三种状态(pending等待态、fulfilled成功态、rejected失败态),一旦状态发生改变后不能再修改状态
  • 每个promise实例都有一个then方法,会有两个参数 onfulfilled onrejected

根据这些特性,我们可以写出基础版本的promise

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
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
// 是一个类
class Promise {
constructor(executor){
this.status = PENDING

// 实例上也能获取
this.value = undefined
this.reason = undefined
const resolve = (value) => {
// 一旦状态发生变化后不能再修改状态
if(this.status === PENDING){
this.value = value
this.status = FULFILLED
}

}
const reject = (reason) => {
if(this.status === PENDING){
this.reason = reason
this.status = REJECTED
}
}
// 如果executor执行发生异常 默认等价reject
try{
executor(const resolve,const reject)
}catch(err){
const reject(err)
}
}
then(onFulfilled,onRejected){
if(this.status === FULFILLED){
onFulfilled(this.value)
}
if(this.status === REJECTED){
onRejected(this.reason)
}
}
}

module.exports = Promise

增加异步处理

上面的版本如果立即修改promise 状态的话能成功,但是promise是用来解决异步问题的,这样就会存在问题

1
2
3
4
5
6
7
8
9
10
11
12
const p1 = new Promise((resolve,reject)=>{
// 异步调用promise会一直处于pending状态,无法正确处理
setTimeout(()=>{
resolve('success')
},2000)
})

p1.then((value)=>{
console.log('success',value)
},(reason)=>{
console.log('error',reason)
})

所以我们需要在原型上增加 onFulfilledCbs,onRejectedCbs 两个数组, 当处于pending状态时分别把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
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
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor(executor){
this.status = PENDING
// 实例上也能获取
this.value = undefined
this.reason = undefined
this.onFulfilledCbs = []
this.onRejectedCbs = []
const resolve = (value) => {
// 一旦状态发生变化后不能再修改状态
if(this.status === PENDING){
this.value = value
this.status = FULFILLED
this.onFulfilledCbs.forEach(fn=> fn())
}

}
const reject = (reason) => {
if(this.status === PENDING){
this.reason = reason
this.status = REJECTED
this.onRejectedCbs.forEach(fn=> fn())
}
}
// 如果executor执行发生异常 默认等价reject
try{
executor(const resolve,const reject)
}catch(err){
const reject(err)
}
}
then(onFulfilled,onRejected){
if(this.status === FULFILLED){
onFulfilled(this.value)
}
if(this.status === REJECTED){
onRejected(this.reason)
}
// 调用then的时候promise没成功也没失败
if(this.status === PENDING){
// this.onFulfilledCbs.push(onFulfilled)
// 这样写是为了能拿到返回值并且方便增加一些自己的逻辑
this.onFulfilledCbs.push(()=>{
// todo...
onFulfilled(this.value)
})
this.onRejectedCbs.push(()=>{
onRejected(this.reason)
})
}
}
}

module.exports = Promise

链式调用

上面的代码.then链式调用的时候就会存在问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const fs = require('fs')
const path = require('path')

// 读取本地文件的操作
function readFile(url){
return new Promise((resolve,reject)=>{
fs.readFile(url,'utf8',function(err,data){
if(err) return reject(err)
resolve(data)
})
})
}

let promise2 = readFile(path.resolve(__dirname, 'name.txt'))
.then((data) => {
// 这个返回值是 x -> x 决定promise外层下一个then是成功还是失败
return 100
})
promise2.then(data => {
console.log('then2 success',data)
}, err => {
console.log('then2 error',err)
})

promise的核心是能进行链式调用,链式调用分为以下几种情况

  • 返回的是一个普通值(非promise得值)这个值会被传到外层then的下一个then的成功中
  • 没有返回值 (抛错了),会执行外层then的下一个then的失败
  • 返回的是promise,会去解析返回的promise将解析后的值,传递到成功或者失败中(看这个promise是什么)

链式调用一般情况需要返回的是this,promise为了能扭转状态 而且还要保证promise状态变化后不可更改,返回一个全新的promise(promise2)

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// ...
then(onFulfilled, onRejected) {
// 链式调用会返回一个全新的promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
try {
// 执行当前then拿到返回值
let x = onFulfilled(this.value)
// 把返回值传给下个then的成功
resolve(x)
} catch (e) {
reject(e)
}
}
if (this.status === REJECTED) {
try {
let x = onRejected(this.reason)
resolve(x)
} catch (e) {
reject(e)
}
}
// 调用then的时候promise没成功也没失败
if (this.status === PENDING) {
this.onFulfilledCbs.push(() => {
// todo... 能拿到返回值
try {
let x = onFulfilled(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
this.onRejectedCbs.push(() => {
try {
let x = onRejected(this.reason)
resolve(x)
} catch (e) {
reject(e)
}
})
}
})
return promise2
}

resolvePromise

上面的链式调用返回的是一个普通值的话已经可以正常链式调用传递给下一个then了,但是如果返回的也是一个promise就需要进一步处理

1
2
3
4
5
6
7
8
9
10
11
12
let promise2 = readFile(path.resolve(__dirname, 'name.txt'))
.then((data) => {
// 这个返回值是 x -> x 决定promise外层下一个then是成功还是失败
// 返回的是一个Promise
return (new Promise(resolve,reject)=>{})
})
promise2.then(data => {
console.log('then2 success',data)
}, err => {
console.log('then2 error',err)
})

就需要我们去解析返回值(A+规范中有具体的要求)

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// 此函数主要目的是判断x是不是promise
function resolvePromise(x,promise2,resolve,reject){
// 用x的值来决定promise2 是成功还是失败 (resolve,reject)
if (x === promise2){
return reject(new TypeError())
}

// promise实例要么是对象要么是函数
if((typeof x === 'object' && x!== null) || (typeof x === 'function')){
let called = false
try{
// 是promise
let then = x.then // 看是否有then方法
if(typeof then === 'function'){
then.call(x,(y)=>{
if(called) return
called = true
resolvePromise(y,promise2,resolve,reject)
},(r)=>{
if(called) return
called = true
reject(r)
})
}else{
// 是对象
resolve(x)
}
}catch(e){
if(called) return
called = true
reject(e)
}
}else{ // 说明x是一个普通值
resolve(x) // 普通值直接向下传递即可
}
}

// ...
then(onFulfilled, onRejected) {
// then方法中如果没有传递参数 那么可以透传到下一个then中
onFulfilled = typeof onFulfilled === 'function'? onFulfilled: data => data
onRejected = typeof onRejected === 'function'? onRejected: reason => {throw reason}
// 链式调用会返回一个全新的promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// 这时候promise2是拿不到的,所以用nextTick
process.nextTick(()=>{
try {
// 执行拿到返回值
let x = onFulfilled(this.value)
// 把返回值传给下个then的成功
resolvePromise(x,promise2,resolve,reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === REJECTED) {
process.nextTick(()=>{
try {
let x = onRejected(this.reason)
resolvePromise(x,promise2,resolve,reject)
} catch (e) {
reject(e)
}
})
}
// 调用then的时候promise没成功也没失败
if (this.status === PENDING) {
this.onFulfilledCbs.push(() => {
process.nextTick(()=>{
// todo... 能拿到返回值
try {
let x = onFulfilled(this.value)
resolvePromise(x,promise2,resolve,reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCbs.push(() => {
process.nextTick(()=>{
try {
let x = onRejected(this.reason)
resolvePromise(x,promise2,resolve,reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
// ...

完整版的promise实现

通过以上步骤就实现了完整版的promise

A+规范中是没有 Promise.all(),Promise.race等(),Promise.resolve()等等方法的要求的,现在已经是一个完整的Promise了

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
function resolvePromise(x,promise2,resolve,reject){
if (x === promise2){
return reject(new TypeError())
}
if((typeof x === 'object' && x!== null) || (typeof x === 'function')){
let called = false
try{
let then = x.then
if(typeof then === 'function'){
then.call(x,(y)=>{
if(called) return
called = true
resolvePromise(y,promise2,resolve,reject)
},(r)=>{
if(called) return
called = true
reject(r)
})
}else{
resolve(x)
}
}catch(e){
if(called) return
called = true
reject(e)
}
}else{
resolve(x)
}

}
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledCbs = []
this.onRejectedCbs = []
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
this.onFulfilledCbs.forEach(fn => fn())
}

}
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onRejectedCbs.forEach(fn => fn())
}
}
try {
executor(const resolve, const reject)
} catch (err) {
const reject(err)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'? onFulfilled: data => data
onRejected = typeof onRejected === 'function'? onRejected: reason => {throw reason}
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
process.nextTick(()=>{
try {
let x = onFulfilled(this.value)
resolvePromise(x,promise2,resolve,reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === REJECTED) {
process.nextTick(()=>{
try {
let x = onRejected(this.reason)
resolvePromise(x,promise2,resolve,reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === PENDING) {
this.onFulfilledCbs.push(() => {
process.nextTick(()=>{
try {
let x = onFulfilled(this.value)
resolvePromise(x,promise2,resolve,reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCbs.push(() => {
process.nextTick(()=>{
try {
let x = onRejected(this.reason)
resolvePromise(x,promise2,resolve,reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
}

如何确定自己实现的promise是否符合 A+ 规范呢

  1. 下载检测工具 yarn global add promise-aplus-tests

  2. 在尾部添加如下代码

1
2
3
4
5
6
7
8
9
10

Promise.deferred = function(){
const dfd = {}
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}

  1. 运行检测 promises-aplus-tests myPromise.js

promise检测结果

哈哈,测试全部通过

其他常用方法的实现

Promise.resolve

  • Promise.resolve 有一个特点就是会产生一个新的promise
1
2
3
4
5
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
resolve(value)
})
}
  • Promise.resolve 可以即系传入的promise 具备等待的效果
1
2
3
4
5
6
7
8
9
10
11
12
13
//...
const resolve = (value) => {
// 为了满足ECMAScript
if(value instanceof Promise){
return value.then(resolve,reject)
}
// 一旦状态发生变化后不能再修改状态
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
this.onFulfilledCbs.forEach(fn => fn())
}
}

Promise.reject

1
2
3
4
5
Promise.reject = function(error){
return new Promise((resolve,reject)=>{
reject(error)
})
}

Promise.catch

1
2
3
catch(errFn){
return this.then(null,errFn) // 针对失败做处理
}

Promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let idx = 0
const result = []
promises.forEach((item,i)=>{
Promise.resolve(item).then((data)=>{
result[i] = data
if(++idx === promises.length){
resolve(result)
}
},()=> reject())
})
})
}

Promise.race

1
2
3
4
5
6
7
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
promises.forEach((item,i)=>{
Promise.resolve(item).then(resolve,reject)
})
})
}

Promise.finally

1
2
3
4
5
6
7
8
9
Promise.prototype.finally = function(fn){
return this.then((val)=>{
// Promise.resolve 具备一个功能 可以解析传入的promise
return Promise.resolve(fn()).then(() => val)
},(r)=>{
return Promise.resolve(fn()).then(()=> {throw r})
})
}