Promise

promise


# API 用法和实现

# 构造器

  • Promise 构造器接收一个函数作为参数,该函数有两个参数
    • resolve 将 promise 由 pending 状态转为 fulfilled 状态的方法
    • reject 将 promise 由 pending 状态转为 rejected 状态的方法
  • Promise 构造器一经调用,传给构造器的函数参数就会立即执行
  • Promise 的状态一旦改变就不可再变
let p = new Promise((resolve, reject) => {
  console.log(1)
  setTimeout(() => {
    console.log(6)
    resolve(2)
  }, 0)
  console.log(3)
  reject(4)
  console.log(5)
})
1
2
3
4
5
6
7
8
9
10

执行结果如下:

1
3
5
// error: Uncaught (in promise) 4
6
1
2
3
4
5

# Promise.prototype.then

  • then 方法返回值是一个新的 Promise 实例
  • then 方法接受两个函数作为参数,且这两个参数是可选的
    • 一个是 promise 变为 fulfilled 状态后的回调,回调的参数是 promise resolve 时传入的值
    • 一个是 promise 变为 rejected 状态后的回调,回调的参数是 promise reject 时传入的拒因或者是未被 catch 的错误
    • 如果两个参数都不传,那么 then 返回的新 Promise 对象就会接受调用这个 then 的原 Promise 的终态作为它的终态
  • then 方法支持链式回调,链式回调的实质是,then 的返回值是一个新的 Promise 实例
  • 可以在同一个 Promise 实例上多次调用 then,最终的表现就是,会将所有的 then 中的回调放到一个队列中,当状态改变后,按顺序挨个调用
let p1 = new Promise((resolve) => {
  console.log('start')
  resolve(1)
})
  .then((res) => {
    console.log('>>>' + res)
    return 2
  })
  .then((res) => {
    console.log('===' + res)
    return Promise.reject(res)
  })
  .then(
    (res) => {
      console.log('---' + res)
    },
    (reason) => {
      console.log('reject:' + reason)
    }
  )

// 多次调用then 方法
p1.then(() => {
  console.log('p1_2')
})
p1.then(() => {
  console.log('p1_3')
})

console.log('end')
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

执行结果如下:

// start
// end
// >>> 1
// === 2
// reject: 2
// p1_2
// p1_3
1
2
3
4
5
6
7
  • then 方法的调用是同步的,then 方法被调用之后,根据状态的不同会把传给它的回调放到了微任务队列里或者 callback 数组中
  • 为什么这里的 p1_2p1_3 是最后输出的?
    • 因为 p1 的引用发生了变化,已经变成上面最后一个 then 方法返回的 Promise 实例,而不是最初 new 的那个了

# Promise.prototype.catch

  • catch 方法的实质是 then 方法只提供失败的回调
  • 该方法也返回一个新的 promise,因为本质上它就是 then 方法
let p = new Promise((resolve, reject) => {
  reject('error 1')
})
  .catch((e) => {
    console.log('catch 1:' + e)
  })
  .catch((e) => {
    console.log('catch 2:' + e)
    throw 'error 2'
  })
  // catch 方法的本质
  .then(undefined, (e) => {
    console.log(e)
  })
1
2
3
4
5
6
7
8
9
10
11
12
13
14

执行结果如下:

'error 1'
'error 2'
1
2

# catch 的原理

Promise.prototype.catch = function (onRejected) {
  return this.then(undefined, onRejected)
}
1
2
3

# Promise.prototype.finally

  • finally 方法是无论 Promise 实例的最终状态是什么,都会被调用的方法
  • 这避免了同样的语句需要在 then()和 catch()中各写一次的情况。
  • finally 方法也有返回值,返回一个设置了 finally 回调函数的 Promise 对象
  • 由于无法知道 promise 的最终状态,所以 finally 的回调函数中不接收任何参数
let p = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000)
})
  .finally(() => {
    console.log('finally')
  })
  .then((res) => {
    console.log(res)
  })
1
2
3
4
5
6
7
8
9

输出结果如下:

finally
1
1
2

# finally 的原理

Promise.prototype.finally = function (fn) {
  // 因为需要透传,所以必须调用this上的then方法
  return this.then(
    (value) => {
      // 放到resolve方法中执行,是因为fn中可能存在异步操作
      return Promise.resolve(fn()).then(() => {
        // 透传value
        return value
      })
    },
    (err) => {
      return Promise.resolve(fn()).then(() => {
        // 异常穿透
        throw err
      })
    }
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Promise.resolve

  • resolve 方法返回一个以给定值解析后的 Promise 对象
    • 如果这个值是一个 promise ,那么将返回这个 promise
    • 如果这个值是 thenable(即带有"then" 方法),返回的 promise 会“跟随”这个 thenable 的对象,采用它的最终状态
    • 否则返回的 promise 将以此值完成
// 值是 promise 实例的情况,将直接返回这个 promise 实例
let p1 = Promise.resolve(
  new Promise((resolve) => {
    resolve(1)
  })
)
p1.then((res) => {
  console.log(res) // 1
})

// 值是 thenable 对象的情况
let p2 = Promise.resolve({
  then: function (onFulfill, onReject) {
    onFulfill('fulfilled!')
  },
})
p2.then((res) => {
  console.log(res) // fulfilled
})

// 值是其他值的情况
let p3 = Promise.resolve(100)
p3.then((res) => {
  console.log(res) // 100
})
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

# resolve 原理

Promise.resolve = function (data) {
  // 如果是一个Promise实例,直接返回
  if (data instanceof Promise) return data

  return new Promise((resolve, reject) => {
    // 如果是一个thenable对象,那返回的promise的状态将跟随这个thenable对象的状态
    if (data.then && typeof data.then === 'function') {
      return data.then(resolve, reject)
    }
    // 其他情况,则直接返回
    resolve(data)
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Promise.reject

  • 静态方法 reject 返回一个带有拒绝原因的 Promise 对象

# reject 原理

Promise.reject = function (reason) {
  return new Promise((_, reject) => reject(reason))
}
1
2
3

# Promise.all

  • 只有当传入的所有 promise 实例都 resolve,该方法返回的 promise 实例才 resolve,并将每个 promise 的结果依次放入结果数组中返回
  • 一旦有一个 promise 被 reject,该方法返回的 promise 就 reject,且返回第一个被 reject 的 promise 的拒因
let genPromise = function (data, reason) {
  return new Promise((resolve, reject) => {
    return reason ? reject(reason) : resolve(data)
  })
}

let p1 = genPromise(1)
let p2 = genPromise(2)
let p3 = genPromise(3)

Promise.all([p1, p2, p3]).then((res) => {
  console.log(res) // [1, 2, 3]
})

let p4 = genPromise(undefined, 4)
let p5 = genPromise(5)
let p6 = genPromise(undefined, 6)

Promise.all([p4, p5, p6]).then(undefined, (reason) => {
  console.log(reason) // 4
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# all 原理

Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises))
      return reject(new TypeError('argument must be an Array'))
    if (!promises.length) return resolve([])
    let results = []
    let len = (_len = promises.length)
    for (let i = 0; i < len; i++) {
      promises[i].then(
        (value) => {
          // 不能直接push,因为不能保证每个promise返回的顺序
          // 错误:results.push(value)
          results[i] = value
          // 都得到结果之后就resolve
          if (!--_len) resolve(results)
        },
        (reason) => {
          reject(reason)
        }
      )
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# Promise.any

  • 目前处于 Stage4 阶段
  • 接收一个 Promise 可迭代对象
  • 只要其中的一个 promise 成功,就返回那个已经成功的 promise,不会等待其他的 promise 全部完成
  • 如果可迭代对象中没有一个 promise 成功,就返回一个失败的 promise 和 AggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起
let genPromise = function (data, reason) {
  return new Promise((resolve, reject) => {
    return reason ? reject(reason) : resolve(data)
  })
}
// 只要有一个成功,就返回那个成功的
let p1 = genPromise(1)
let p2 = genPromise(undefined, 2)
let p3 = genPromise(3)

Promise.any([p1, p2, p3]).then((res) => {
  console.log(res) // 1
})

// 所有都不成功,则返回一个失败的promise
let p4 = genPromise(undefined, 4)
let p5 = genPromise(undefined, 5)
let p6 = genPromise(undefined, 6)

Promise.any([p4, p5, p6]).then(undefined, (reason) => {
  console.log(reason) // 'AggregateError: All promises were rejected'
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# any 原理

Promise.any() 接收一个 Promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和 AggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。

  • 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise。
  • 如果传入的参数不包含任何 promise,则返回一个 异步完成 (asynchronously resolved)的 Promise。
Promise.any = function (promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises))
      return reject(new TypeError('argument must be an Array'))
    if (!promises.length)
      return reject(new AggregateError('All promises were rejected'))
    let len = promises.len
    for (let p of promises) {
      p.then(
        (value) => {
          resolve(value)
        },
        (reject) => {
          if (!--len)
            return reject(new AggregateError('All promises were rejected'))
        }
      )
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Promise.race

  • 返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝
let genPromise = function (data, reason) {
  return new Promise((resolve, reject) => {
    return reason ? reject(reason) : resolve(data)
  })
}
// 一旦某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝
let p1 = genPromise(1)
let p2 = genPromise(undefined, 2)
let p3 = genPromise(3)

Promise.race([p1, p2, p3]).then((res) => {
  console.log(res) // 1
})

// 一旦某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝
let p4 = genPromise(undefined, 4)
let p5 = genPromise(5)
let p6 = genPromise(undefined, 6)

Promise.race([p4, p5, p6]).then(undefined, (reason) => {
  console.log(reason) // 4
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# race 原理

Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) return reject(promises + 'must be an array')
    for (let p of promises) {
      p.then(resolve, reject)
    }
  })
}
1
2
3
4
5
6
7
8

# Promise.allSettled

  • 返回一个在所有给定的 promise 都已经 fulfilled 或 rejected后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果
  • 当有多个彼此不依赖的异步任务成功完成时,通常使用它
let genPromise = function (data, reason) {
  return new Promise((resolve, reject) => {
    return reason ? reject(reason) : resolve(data)
  })
}
// 等待所有promise都变成完成态,返回结果数组
let p1 = genPromise(1)
let p2 = genPromise(undefined, 2)
let p3 = genPromise(3)

Promise.allSettled([p1, p2, p3]).then((res) => {
  console.log(res)
  /**
   * 0: {state: 'fulfilled', value: 1}
   * 1: {state: 'rejected', reason: 2}
   * 2: {state: 'fulfilled', value: 3}
   * */
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# allSettled 原理

Promise.allSettled = function (promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) return reject(promises + 'must be an array')
    let res = []
    let len = promises.length
    let done = () => {
      if (res.length === len) resolve(res)
    }
    for (let i = 0; i < len; i++) {
      promises[i].then(
        (data) => {
          res[i] = {
            state: 'fulfilled',
            value: data,
          }
          done()
        },
        (reason) => {
          res[i] = {
            state: 'rejected',
            reason,
          }
          done()
        }
      )
    }
  })
}
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

# 手写 Promise

上面的 API 大部分都已经实现了,现在看下 Promise 本身的实现,参考Promise A+规范 (opens new window)

# 第一步

  • Promise 实例有三个状态:Pending | Rejected | Fulfilled
  • 接受一个函数作为参数,实例化时,该函数被立即调用
  • 函数参数拥有两个参数:resolve & reject,这是两个方法
    • 当 resolve 方法被调用时,Promise 的状态由 Pending 变为 Fulfilled
    • 当 reject 方法被调用时,Promise 的状态由 Pending 变为 Rejected
class Promise {
  static PENDING = 'pending'
  static FULFILLED = 'fulfilled'
  static REJECTED = 'rejected'

  constructor(excutor) {
    // 用于记录Promise变为fulfilled时要传递的值
    this.value = void 0
    // 用于记录Promise变为Rejected时要传递的拒因
    this.reason = void 0
    // Promise实例的初始状态
    this.state = Promise.PENDING

    // 用于将promise从pending状态变为fulfilled状态的方法
    let resolve = (data) => {
      if (this.state === Promise.PENDING) {
        this.data = data
        this.state = Promise.FULFILLED
      }
    }
    // 用于将promise从pending状态变为rejected状态的方法
    let reject = (reason) => {
      if (this.state === Promise.PENDING) {
        this.reason = reason
        this.state = Promise.REJECTED
      }
    }

    // 构造器一经调用,函数参数就立即执行
    try {
      excutor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
}
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

ok,第一步就已经完成了

# 第二步

# then 方法

  • then 方法接收两个函数作为参数
    • 这两个参数都是可选的,非必填
    • onFulFilled 函数,接受一个 value 作为 Promise 已解决状态的返回值
    • onRejected 函数,接收一个 reason 作为 Promise 已拒绝状态的拒因
    • 这两个函数都是异步调用的
  • then 方法的返回值是一个新的 Promise 实例,与调用 then 方法的 Promise 实例不能是同一个
    • 这使得 then 方法可以链式调用
  • then 方法可以在一个 promise 实例上多次调用
    • 被注册的回调,会在 Promise 实例状态发生改变时,依次按顺序被调用
class Promise {
  constructor() {
    // ... 省略其他
    this.onFulfilledCb = []
    this.onRejectedCb = []
    // ... 省略其他
  }

  then(onFulfilled, onRejected) {
    // 这两个参数必须是函数类型,如果不是进行兜底处理
    onFulfilled =
      typeof onFulfilled !== 'function' ? (value) => value : onFulfilled
    onRejected =
      typeof onRejected !== 'function'
        ? (reason) => {
            throw reason
          }
        : onRejected
    // then方法返回一个新的promise
    let p2 = new Promise((resolve, reject) => {
      // onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用,这里进行统一包装,并 catch 异常
      let wrappedOnFulfilledCb = () => {
        queueMicrotask(() => {
          try {
            let x = onFulfilled(this.value)
            // 根据标准,如果 onFulfilled 方法返回一个值,则需要运行下面的 Promise 解决过程
            resolePromise(p2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      let wrappedOnRejectedCb = () => {
        queueMicrotask(() => {
          try {
            let x = onRejected(this.reason)
            // 根据标准,如果 onRejected 方法返回一个值,则也需要运行下面的 Promise 解决过程
            resolePromise(p2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      // 如果是 pending 状态,则把回调先暂存,等状态改变后再调用
      if (this.state === Promise.PENDING) {
        this.onFulfilledCb.push(wrappedOnFulfilledCb)
        this.onRejectedCb.push(wrappedOnRejectedCb)
      }
      // 如果是 fulfilled 状态
      if (this.state === Promise.FULFILLED) {
        wrappedOnFulfilledCb()
      }
      // 如果是 rejected 状态
      if (this.state === Promise.REJECTED) {
        wrappedOnRejectedCb()
      }
    })
    return p2
  }
}
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

所以,then 方法本身是同步调用的,传给它的两个回调才是放到微任务队列异步执行的

# catch 方法

class Promise {
  // ...省略其他
  catch(fn) {
    return this.then(undefined, fn)
  }
}
1
2
3
4
5
6

# 第三步

# promise 解决过程

过程相对来说步骤比较多,不过很好理解,按照标准一步一步写就行了

  • 如果 x 与 p2 是同一个对象,则返回一个 TypeError

这主要是为了避免这种情况:

let p1 = new Promise((resolve) => {
  resolve(1)
})

let p2 = p1.then((value) => {
  return p2
})
// TypeError: Chaining cycle detected for promise #<Promise>
1
2
3
4
5
6
7
8
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise
  • 如果 x 是一个对象(包括 promise 对象),或者函数
    • 把 x.then 赋值给 then
    • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
    • 如果 then 是函数,将 x 作为函数的作用域 this 调用之,传递两个回调函数作为参数
      • 第一个参数叫做 resolvePromise 第二个参数叫做 rejectPromise
      • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
      • 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
      • 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
      • 如果调用 then 方法抛出了异常 e
        • 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
        • 否则以 e 为据因拒绝 promise
      • 如果 then 不是函数,以 x 为参数执行 promise

# 实现

function resolvePromise(p2, x, resolve, reject) {
  if (p2 === x) {
    return reject(
      new TypeError('Chaining cycle detected for promise #<Promise>')
    )
  }
  if ((typeof x === 'object' || typeof x === 'function') && x !== null) {
    let called = false
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(
          x,
          (y) => {
            if (called) return
            called = true
            resolvePromise(p2, y, 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)
  }
}
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

# 完整代码

class Promise {
  static PENDING = 'pending'
  static FULFILLED = 'fulfilled'
  static REJECTED = 'rejected'

  constructor(excutor) {
    // 用于记录Promise变为fulfilled时要传递的值
    this.value = void 0
    // 用于记录Promise变为Rejected时要传递的拒因
    this.reason = void 0
    // Promise实例的初始状态
    this.state = Promise.PENDING
    // 解决多次调用 then 方法的场景
    this.onFulfilledCb = []
    this.onRejectedCb = []

    // 使用箭头函数,绑定this指向
    let resolve = (value) => {
      if (this.state === Promise.PENDING) {
        this.value = value
        this.state = Promise.FULFILLED
        this.onFulfilledCb.forEach((cb) => cb())
      }
    }
    // 使用箭头函数,绑定this指向
    let reject = (reason) => {
      if (this.state === Promise.PENDING) {
        this.reason = reason
        this.state = Promise.REJECTED
        this.onRejectedCb.forEach((cb) => cb())
      }
    }

    // 构造器一经调用,函数参数就立即执行
    try {
      excutor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  then(onFulfilled, onRejected) {
    // 这两个参数必须是函数类型,如果不是进行兜底处理
    onFulfilled =
      typeof onFulfilled !== 'function' ? (value) => value : onFulfilled
    onRejected =
      typeof onRejected !== 'function'
        ? (reason) => {
            throw reason
          }
        : onRejected
    // then方法返回一个新的promise
    let p2 = new Promise((resolve, reject) => {
      // onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用,这里进行统一包装,并 catch 异常
      let wrappedOnFulfilledCb = () => {
        queueMicrotask(() => {
          try {
            let x = onFulfilled(this.value)
            // 根据标准,如果 onFulfilled 方法返回一个值,则需要运行下面的 Promise 解决过程
            resolvePromise(p2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      let wrappedOnRejectedCb = () => {
        queueMicrotask(() => {
          try {
            let x = onRejected(this.reason)
            // 根据标准,如果 onRejected 方法返回一个值,则也需要运行下面的 Promise 解决过程
            resolvePromise(p2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      // 如果是 pending 状态,则把回调先暂存,等状态改变后再调用
      if (this.state === Promise.PENDING) {
        this.onFulfilledCb.push(wrappedOnFulfilledCb)
        this.onRejectedCb.push(wrappedOnRejectedCb)
      }
      // 如果是 fulfilled 状态
      if (this.state === Promise.FULFILLED) {
        wrappedOnFulfilledCb()
      }
      // 如果是 rejected 状态
      if (this.state === Promise.REJECTED) {
        wrappedOnRejectedCb()
      }
    })
    return p2
  }

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

  static resolve(data) {
    if (data instanceof Promise) return data
    return new Promise((resolve, reject) => {
      try {
        if (data.then && typeof data.then === 'function') {
          data.then(resolve, reject)
        } else {
          resolve(data)
        }
      } catch (error) {
        reject(error)
      }
    })
  }

  static reject(reason) {
    return new Promise((_, reject) => {
      reject(reason)
    })
  }
}

function resolvePromise(p2, x, resolve, reject) {
  // 整个过程就按照标准一步步实现即可
  if (p2 === x) {
    return reject(
      new TypeError('Chaining cycle detected for promise #<Promise>')
    )
  }

  if (x !== null && (typeof x === 'object' || 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(p2, y, 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)
  }
}
// ======== 跑测试用例使用 ========= //
// Promise.deferred = function () {
//   let dfd = {}
//   dfd.promise = new Promise((resolve, reject) => {
//     dfd.resolve = resolve
//     dfd.reject = reject
//   })
//   return dfd
// }

// module.exports = 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
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

# 测试结果

测试工具 (opens new window)

# 面试题

# 输出顺序

# 第一个

const first = () =>
  new Promise((resolve, reject) => {
    console.log(3)
    let p = new Promise((resolve, reject) => {
      console.log(7)
      setTimeout(() => {
        console.log(5)
        resolve(6)
      }, 0)
      resolve(1)
    })
    resolve(2)
    p.then((arg) => {
      console.log(arg)
    })
  })

first().then((arg) => {
  console.log(arg)
})
console.log(4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

输出结果如下:

3
7
4
1
2
5
1
2
3
4
5
6
  • first 被调用,Promise 构造器执行,先输出 3
  • first 内部第二个 Promise 构造器执行,输出 7
  • 遇到 setTimeout 放到 Tasks 队列,主线程继续向下执行
  • resolve(1) 将 p 的状态改为 fulfilled
  • resolve(2) 将外部 promise 的状态改为 fulfilled
  • p.then 注册第一个微任务,first 内部逻辑执行完成
  • first().then 方法注册第二个微任务
  • 输出 4,此时主线程为空,开始检查微任务队列
  • 两个 promise 的状态现在都是 fulfilled,首先调用 p.then 注册的微任务,输出 1
  • 然后调用 first().then 注册的微任务,输出 2。微任务队列为空,转而检查 Tasks 队列
  • 定时器已到时间,取出并执行,输出 5
  • resolve(6) 被忽略,因为 promise 的状态已经是已完成了

# 第二个

这道题虽然看似简单,但其实里面的运行机制基本上涉及到了 Promise 的所有流程

let p1 = new Promise((resolve) => {
  resolve(1)
})
  .then((v) => {
    console.log(v)
    return v + 1
  })
  .then((v) => {
    console.log(v)
    return v + 1
  })
  .then((v) => {
    console.log(v)
    return v + 1
  })

let p10 = new Promise((resolve) => {
  resolve(10)
})
  .then((v) => {
    console.log(v)
    return v + 1
  })
  .then((v) => {
    console.log(v)
    return v + 1
  })
  .then((v) => {
    console.log(v)
    return v + 1
  })
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

输出结果如下:

1
10
2
11
3
12
1
2
3
4
5
6

交替输出,你能解释清楚这其中的原因吗?

  • p1 构造器执行,p1 立即 resolve,变为 fulfilled
  • p1.then 的调用会将注册的回调放入到微任务队列;then 方法执行返回一个新的 promise,我们叫它 p2(依次类推 p3、p4)
  • 然后 p2、p3、p4 的 then 方法也会依次执行(因为 then 方法是同步调用),但是此时的 p2、p3、p4 还都是 pending 状态,所以它们的回调会被放到各自的 callback 数组里暂存起来而不是微任务队列里。这里可以结合代码来看
let wrappedOnFulfilledCb = () => {
  queueMicrotask(() => {
    try {
      let x = onFulfilled(this.value)
      // 根据标准,如果 onFulfilled 方法返回一个值,则需要运行下面的 Promise 解决过程
      resolvePromise(p2, x, resolve, reject)
    } catch (e) {
      reject(e)
    }
  })
}

// 如果是 pending 状态,则把回调先暂存,等状态改变后再调用
if (this.state === Promise.PENDING) {
  this.onFulfilledCb.push(wrappedOnFulfilledCb)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

可以看到 pending 状态下,各自维护的 callback 数组中存储的是一个被 wrap 后的回调,将真正的用户传入的回调推入微任务队列的时机则是在这个 wrap 后的回调被执行之后

那这个被 wrap 的回调被执行的地方是在哪里呢?咱们以 wrappedOnFulfilledCb 这个回调来看,它被调用的时机有两个

// 1、是在then方法被调用时 且 Promise的状态不为pending的时候
if (this.state === Promise.FULFILLED) {
  wrappedOnFulfilledCb()
}

// 2、就是在 resolve 方法里,这种情况下,当resolve被调用的时候,才会真正的将用户的回调放到微任务队列中去
let resolve = (value) => {
  if (this.state === Promise.PENDING) {
    this.value = value
    this.state = Promise.FULFILLED
    // 这里执行的 callback 其实只是被 wrap 后的回调,也就是上面的 wrappedOnFulfilledCb
    // 它执行的结果就是 将用户真正的回调放到微任务队列里
    this.onFulfilledCb.forEach((cb) => cb())
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

ok,继续

  • p10 的流程同上
  • 主线程任务执行完毕,检查微任务队列,此时微任务队列中仅有 p1 和 p10,依次拿出他们注册的回调并执行,分别输出 1 和 10
  • p1 和 p10 的回调执行的过程中,又会将 p2 和 p11 的状态置为 fulfilled,并将他们的 then 回调放到微任务队中去
  • 这一步则是在 resolvePromise 的过程中,调用 resolve 方法时做到的
  • 然后重复上面的步骤,直到所有微任务被清空

所以最终执行的结果就是交替输出的

# 每隔 1 秒输出 1,2,3

使用 promise 实现,每隔一秒输出 1,2,3

;[1, 2, 3].reduce((p, x) => {
  return p.then((res) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log(x)
        resolve()
      }, 1000)
    })
  })
}, Promise.resolve())
1
2
3
4
5
6
7
8
9
10

# 红绿灯交替闪烁

红灯 3 秒亮一次,黄灯 2 秒亮一次,绿灯 1 秒亮一次;如何让三个灯不断交替、循环亮灯

三个亮灯函数:

function red() {
  console.log('red')
}
function green() {
  console.log('green')
}
function yellow() {
  console.log('yellow')
}
1
2
3
4
5
6
7
8
9

# 实现

  • 利用递归实现不断重复的效果
let sleep = (time, fn) =>
  new Promise((resolve) => {
    setTimeout(() => {
      fn()
      resolve()
    }, time * 1000)
  })

let step = () => {
  return Promise.resolve()
    .then(() => {
      return sleep(3, red)
    })
    .then(() => {
      return sleep(2, green)
    })
    .then(() => {
      return sleep(1, yellow)
    })
    .then(() => {
      step() // 使用递归用来实现不断重复的效果
    })
}

step()
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

# 实现异步并发控制

/**
 * 异步并发限制
 *
 * @export
 * @param {Array} sources
 * @param {*} callback
 * @param {*} limit
 * @returns
 */
async function limitAsyncConcurrency(sources, callback, limit = 5) {
  let done
  let lock = []
  let results = []
  let runningCount = 0
  let total = sources.length
  if (!total) return

  const p = new Promise((resolve) => (done = resolve))

  const block = async () => {
    return new Promise((resolve) => lock.push(resolve))
  }

  // 解除lock
  const next = () => {
    const fn = lock.shift()
    fn && fn()
    runningCount--
  }

  const excutor = async (index, item) => {
    // 限制并发
    if (runningCount >= limit) await block()
    runningCount++

    new Promise((resolve, reject) =>
      callback(index, item, resolve, reject)
    ).then((res) => {
      total--
      next()
      results[index] = res
      if (!total) {
        done(results)
      }
    })
  }

  for (const [index, item] of sources.entries()) {
    excutor(index, item)
  }

  return p
}

let sources = ['1.text', '2.txt', '3.txt', '4.txt', '5.txt', '6.txt', '7.txt']
let getFile = (index, file, resolve, reject) => {
  setTimeout(() => {
    console.log(index, file)
    resolve(file)
  }, 1000)
}

imitAsyncConcurrency(sources, getFile, 3).then((res) => {
  console.log(res)
})
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

输出结果如下:

0 '1.text'
1 '2.txt'
2 '3.txt'
// 间隔 1s
3 '4.txt'
4 '5.txt'
5 '6.txt'
// 间隔 1s
6 '7.txt'

// 最后输出
['1.text', '2.txt', '3.txt', '4.txt', '5.txt', '6.txt', '7.txt']
1
2
3
4
5
6
7
8
9
10
11
12
Last Updated: 10/21/2024, 4:15:17 PM