Promise 规范和应用

1、参考资料

1、 promise A plus 规范(opens new window)open in new window

2、【第 1738 期】100 行代码实现 Promises/A+ 规范(opens new window)open in new window

3、JS 常见问题整理open in new window

4、史上最详细手写 promise 教程(opens new window)open in new window

PromiseA+规范

1、术语

  • promise 是一个有 then 方法的对象或者函数,行为遵循本规范
  • thenable 是一个有 then 方法的对象或者是函数
  • value 是 promise 状态成功时的值,也就是 resolve 的参数,指各种 js 值(包括 undefined、thenable 和 promise)
  • reason 是 promise 状态失败时的值,也就是 reject 的参数,表示拒绝的原因
  • exception 是一个使用 throw 抛出的异常值

2、Promise States

promise 有三种状态,注意他们之间的流转关系

1、pending

  • 初始状态,可改变
  • 一个 promise 在 resolve 或者 reject 之前都处于这个状态
  • 可通过 resolve 转变为 fulfilled 状态
  • 可通过 reject 转变为 rejected 状态

2、fulfilled

  • 最终态,不可变
  • 一个 promise 被 resolve 后会变为这个状态
  • 必须拥有一个 value 值

3、rejected

  • 最终态,不可变
  • 一个 promise 被 reject 后会变为这个状态
  • 必须拥有一个 reason 状态流转如下: pending -> resolve(value) -> fulfilled pending -> reject(reason) -> rejected

3、then

promise 应该有个 then 方法,用来访问最终的结果,无论是 value 还是 reason。

promise.then(onFulfilled, onRejected)
1

1、参数要求

  • onFulfilled 必须是函数类型,可选,如果不是函数,应该被忽略
  • onRejected 必须是函数类型,可选,如果不是函数,应该被忽略

2、onFulfilled 特性

  • 在 promise 变为 fulfilled 时,应该调用 onFulfilled,参数是 value
  • 在 promise 变成 fulfilled 之前,不应该被调用
  • 只能被调用一次(实现时需使用变量来限制执行次数)

3、onRejected 特性

  • 在 promise 变成 rejected 时,应该调用 onRejected,参数是 reason
  • 在 promise 变成 rejected 之前,不应该被调用
  • 只能被调用一次

4、onFulfilled 和 onRejected 应该是微任务

在执行上下文堆栈仅包含平台代码之前,不得调 onFulfilled 或 onRejected 函数,onFulfilled 和 onRejected 必须被作为普通函数调用(即非实例化调用,这样函数内部 this 非严格模式下指向 window),使用 queueMicrotask 或者 setTimeout 来实现微任务的调用

5、then 方法可以被调用多次

  • promise 状态变成 fulfilled 后,所有的 onFulfilled 回调都需要按照 then 的顺序执行,也就是按照注册顺序执行(实现时用数组存储多个 onFulfilled 的回调)
  • promise 状态变成 rejected 后,所有的 onRejected 回调都需要按照 then 的顺序执行,也就是按照注册顺序执行(实现时用数组存储多个 onRejected 的回调)

6、then 必须返回一个 promise

then 必须返回一个 promise

promise2 = promise1.then(onFulfilled, onRejected)
1
  • onFulfilled 或 onRejected 执行的结果是 x,调用 resolvePromise
  • 如果 onFulfilled 或者 onRejected 执行时抛出异常 e,promise2 需要被 reject,其 reason 为 e
  • 如果 onFulfilled 不是一个函数且 promise1 已经 fulfilled,promise2 以 promise1 的 value 触发 onFulfilled
  • 如果 onRejected 不是一个函数且 promise1 已经 rejected,promise2 以 promise1 的 reason 触发 onRejected

7、Promise 的解决过程 resolvePromise

resolvePromise(promise2, x, resolve, reject)
1
  • 如果 x 是当前 promise 本身(promise2 和 x 相等),那么 reject TypeError
  • 如果 x 是另一个 promise(即 x 是一个 promise),那么沿用它的 state 和 result 状态
    • 如果 x 是 pending 态,那么 promise 必须要在 pending,直到 x 变成 fulfilled 或者 rejected
    • 如果 x 是 fulfilled 态,用相同的 value 执行 promise
    • 如果 x 是 rejected 态,用相同的 reason 拒绝 promise
  • 如果 x 是一个 object 或者是一个 function(不常见)
    • 首先取 x.then 的值,let then = x.then
    • 如果取 x.then 这步出错抛出 e,那么以 e 为 reason 拒绝 promise
    • 如果 then 是一个函数,将 x 作为函数的作用域 this 调用,即 then.call(x, resolvePromise, rejectPromise),第一个参数叫 resolvePromise,第二个参数叫 rejectPromise
      • 如果 resolvePromise 以 y 为参数被调用,则执行 resolvePromise(promise2, y, resolve, reject)
      • 如果 rejectPromise 以 r 为参数被调用,则以 r 为 reason 拒绝 promise
      • 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
      • 如果调用 then 抛出异常 e:若 resolvePromise 或 rejectPromise 已经被调用,那么忽略,否则以 e 为 reason 拒绝 promise
    • 如果 then 不是一个 function,以 x 为 value 执行 promise
  • 如果 x 不是 object 或者 function,以 x 为 value 执行 promise

4、promise A+实现

1、定义三种状态,设置初始态,使用 class 实现类

const PENDING = "pending"
const REJECTED = "rejected"
const FULFILLED = "fulFilled"
class MPromise {
  constructor() {
    this.status = PENDING
    this.value = null
    this.reason = null
    this.onResolvedCallbacks = [] //pending时回调函数存储
    this.onRejectedCallbacks = []
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

2、添加 reject 和 resolve 方法更改状态,执行回调

reject(reason){
    if(this.status === PENDING){
        this.status = REJECTED
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn => fn())
    }
}

resolve(value){
    if(this.status === PENDING){
        this.status = FULFILLED
        this.value = value
        this.onResolvedCallbacks.forEach(fn => fn())
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

3、promise 添加入参函数,函数同步执行,异常需 reject

constructor(fn){
    this.status = PENDING
    this.value = null
    this.reason = null
    this.onResolvedCallbacks = []
    this.onRejectedCallbacks = []
    try{
        fn(this.resolve.bind(this), this.reject.bind(this))
    }catch(e){
        this.reject(e)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

4、实现 then 方法

1、入参为 onFulfilled 和 onRejected 并检查参数,如果不是 function 就忽略,原样返回 value 或者 reason 2、最终返回 promise,根据不同状态执行 onFulfilled 和 onRejected,执行异常则 reject,执行正常则执行 resovlePromise(promise2, x, resolve, reject)。注意:rejected 状态执行 onRejected,Fulfilled 状态执行 onFulfilled,pending 状态时收集回调至数组,待状态变更时执行,可使用 ES6 的 getter 和 setter 监听状态变换并执行,也可以直接放在 resolve 和 reject 方法中回调。 3、onFulfilled 和 onRejected 是微任务,使用 queueMicrotask 包裹或 setTimeout 包裹

isFunction(fn){
    return typeof fn === 'function'
}

then(onFulfilled, onRejected){
    //入参为onFulfilled和onRejected并检查参数,如果不是function 就忽略,原样返回value或者reason
    onFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value => value
    onRejected = this.isFunction(onRejected) ? onRejected : err => {throw err}
    //最终返回promise
    let promise2 = new MPromise((resolve, reject) => {
        //根据不同状态执行onFulfilled和onRejected,执行异常则reject,执行正常返回x则执行resovlePromise
        if(this.status === FULFILLED){
            //onFulfilled和onRejected执行是微任务
            setTimeout(()=>{
                try{
                    let x = onFulfilled(this.value)
                    this.resovlePromise(promise2, x, resolve, reject)
                }catch(e){
                    reject(e)
                }
            }, 0)
        }
        if(this.status === REJECTED){
            setTimeout(()=>{
                try{
                    let x = onRejected(this.reason)
                    this.resovlePromise(promise2, x, resolve, reject)
                }catch(e){
                    reject(e)
                }
            }, 0)
        }
                //pending状态时收集回调至数组
        if(this.status === PENDING){
            this.onResolvedCallbacks.push(()=>{
                setTimeout(()=>{
                    try{
                        let x = onFulfilled(this.value)
                        this.resovlePromise(promise2, x, resolve, reject)
                    }catch(e){
                        reject(e)
                    }
                }, 0)
            })

            this.onRejectedCallbacks.push(()=>{
                setTimeout(()=>{
                    try{
                        let x = onRejected(this.reason)
                        this.resovlePromise(promise2, x, resolve, reject)
                    }catch(e){
                        reject(e)
                    }
                }, 0)
            })
        }
    })
    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
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

5、resolvePromise 实现

resovlePromise(newPromise, x, resolve, reject){
    // 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
    // 这是为了防止死循环
    if(newPromise === x){
        return reject(new TypeError('The promise and the return value are the same'))
    }

    //如果x是一个promise,那么沿用它的 state 和 result 状态
    if(x instanceof MPromise){
        x.then((y)=>{
            this.resovlePromise(newPromise, y, resolve, reject)
        }, reject)
    }

    //如果x是一个对象或者函数,不为null
    if(x !== null && (typeof x === 'object' || this.isFunction(x))){
        let then = null
        try{
            then = x.then
        }catch(e){
            reject(e)
        }

        if(this.isFunction(then)){
            //添加是否调用过的标志
            let called = false
            try{
                then.call(x,
                (y)=>{
                    if(called) return
                    called = true
                    this.resovlePromise(newPromise, y, resolve, reject)
                },
                (r)=>{
                    if(called) return
                    called = true
                    reject(r)
                })
            }catch(e){
                if(called) return
                reject(e)
            }
        }else{
            resolve(x)
        }
    }else{
        resolve(x)
    }
}
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

6、Promise.reject 和 Promise.resolve 及 catch 实现

catch(onRejected){
    return this.then(null, onRejected)
}

static resolve(value){
    if(value instanceof MPromise){
        return value
    }
    return new MPromise(function(resolve){
        resolve(value)
    })
}

static reject(reason){
    return new MPromise(function(resolve, reject){
        reject(reason)
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

7、promise.race 和 promise.all 实现

race(promiseList){
    return new MPromise((resolve, reject)=>{
        if(!Array.isArray(promiseList)){
            reject('input param must be a list')
        }
        let size = promiseList.length
        if(size === 0){
            resolve()
        }else{
            for(let i=0; i<size; i++){
                MPromise.resolve(promiseList(i)).then(
                    (value)=>{
                        resolve(value)
                    },
                    (reason)=>{
                        reject(reason)
                    }
                )
            }
        }
    })
}

all(promiseList){
    return new MPromise((resolve, reject)=>{
        if(!Array.isArray(promiseList)){
            reject('input param must be a list')
        }
        let size = promiseList.length
        if(size === 0){
            resolve()
        }else{
            let count = 0
            let res = []
            for(let i=0; i<size; i++){
                MPromise.resolve(promiseList(i)).then((value)=>{
                    res[i] = value
                    if(count++ === size){
                        resolve(res)
                    }
                }).catch((reason)=>{
                    reject(reason)
                })
            }
        }
    })
}

//返回所有promise的状态和结果
allSettled(promiseList){
  return new MPromise((resolve, reject)=>{
    if(!Array.isArray(promiseList)){
      return reject(new TypeError('arguments must be an array'))
    }
    let counter =0
    const promiseNum = promiseList.length
    const resolvedArray = []
    for(let i=0; i<promiseNum; i++){
      MPromise.resolve(promiseList[i]).then(value => {
        resolvedArray[i] = {
          status: 'fulfilled',
          value
        }
      }).catch(reason =>{
        resolvedArray[i] = {
          status: 'rejected',
          reason
        }
      }).finally(()=>{
        if(counter++ == promiseNum){
          resolve(resolvedArray)
        }
      })
    }
  })
}
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

5、全量代码

const PENDING = "pending"
const REJECTED = "rejected"
const FULFILLED = "fulfilled"

class MPromise {
  constructor(fn) {
    this.status = PENDING
    this.value = null
    this.reason = null
    this.onResolvedCallbacks = []
    this.onRejectedCallbacks = []
    try {
      fn(this.resolve.bind(this), this.reject.bind(this))
    } catch (e) {
      this.reject(e)
    }
  }

  reject(reason) {
    if (this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
      this.onRejectedCallbacks.forEach(fn => fn())
    }
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
      this.onResolvedCallbacks.forEach(fn => fn())
    }
  }

  isFunction(fn) {
    return typeof fn === "function"
  }

  then(onFulfilled, onRejected) {
    //入参为onFulfilled和onRejected并检查参数,如果不是function 就忽略,原样返回value或者reason
    onFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value => value
    onRejected = this.isFunction(onRejected)
      ? onRejected
      : err => {
          throw err
        }
    //最终返回promise
    let promise2 = new MPromise((resolve, reject) => {
      //根据不同状态执行onFulfilled和onRejected,执行异常则reject,执行正常返回x则执行resovlePromise
      if (this.status === FULFILLED) {
        //onFulfilled和onRejected执行是微任务
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            this.resovlePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            this.resovlePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }

      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value)
              this.resovlePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              this.resovlePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    return promise2
  }

  resovlePromise(promise2, x, resolve, reject) {
    // 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
    // 这是为了防止死循环
    if (promise2 === x) {
      return reject(new TypeError("The promise and the return value are the same"))
    }

    //如果x是一个promise,那么沿用它的 state 和 result 状态
    if (x instanceof MPromise) {
      x.then(y => {
        this.resovlePromise(promise2, y, resolve, reject)
      }, reject)

      //如果x是一个对象或者函数,不为null
    } else if (x !== null && (typeof x === "object" || this.isFunction(x))) {
      let then = null
      try {
        then = x.then
      } catch (e) {
        reject(e)
      }

      if (this.isFunction(then)) {
        //添加是否调用过的标志
        let called = false
        try {
          then.call(
            x,
            y => {
              if (called) return
              called = true
              this.resovlePromise(promise2, y, resolve, reject)
            },
            r => {
              if (called) return
              called = true
              reject(r)
            }
          )
        } catch (e) {
          if (called) return
          reject(e)
        }
      } else {
        resolve(x)
      }
    } else {
      resolve(x)
    }
  }

  catch(onRejected) {
    return this.then(null, onRejected)
  }

  static resolve(value) {
    if (param instanceof MPromise) {
      return value
    }
    return new MPromise(function (resolve) {
      resolve(value)
    })
  }

  static reject(reason) {
    return new MPromise(function (resolve, reject) {
      reject(reason)
    })
  }

  race(promiseList) {
    return new MPromise((resolve, reject) => {
      if (!Array.isArray(promiseList)) {
        reject("input param must be a list")
      }
      let size = promiseList.length
      if (size === 0) {
        resolve()
      } else {
        for (let i = 0; i < size; i++) {
          MPromise.resolve(promiseList(i)).then(
            value => {
              resolve(value)
            },
            reason => {
              reject(reason)
            }
          )
        }
      }
    })
  }

  all(promiseList) {
    return new MPromise((resolve, reject) => {
      if (!Array.isArray(promiseList)) {
        reject("input param must be a list")
      }
      let size = promiseList.length
      if (size === 0) {
        resolve()
      } else {
        let count = 0
        let res = []
        for (let i = 0; i < size; i++) {
          MPromise.resolve(promiseList(i))
            .then(value => {
              res[i] = value
              if (count++ === size) {
                resolve(res)
              }
            })
            .catch(reason => {
              reject(reason)
            })
        }
      }
    })
  }

  static deferred() {
    let dfd = {}
    dfd.promise = new MPromise((resolve, reject) => {
      dfd.resolve = resolve
      dfd.reject = reject
    })
    return dfd
  }
}

module.exports = MPromise
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
Last Updated:
Contributors: rumengkai