Promise总结

665 阅读16分钟

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
3
5
// error: Uncaught (in promise) 4
6

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')

执行结果如下:

// start
// end
// >>> 1
// === 2
// reject: 2
// p1_2
// p1_3
  • then 方法的调用是同步的,then 方法被调用之后,根据不同状态,会把传给它的回调放到了微任务队列里或者自身callback数组里
  • 为什么这里的 p1_2p1_3 是在最后输出的?(注意p1的引用变化)

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)
  })

执行结果如下:

'error 1'
'error 2'

catch 的原理

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

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)
  })

输出结果如下:

finally
1

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
      })
    }
  )
}

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
})

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)
  })
}

Promise.reject

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

reject 原理

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

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
})

all 原理

Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(arr))
      return reject(new TypeError('argument must be an Array'))
    if (!arr.length) return resolve([])
    let results = []
    let 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)
        }
      )
    }
  })
}

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'
})

any 原理

  • 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise。
  • 如果传入的参数不包含任何 promise,则返回一个 异步完成 (asynchronously resolved)的 Promise。
Promise.any = function (promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(arr))
      return reject(new TypeError('argument must be an Array'))
    if (!arr.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'))
        }
      )
    }
  })
}

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
})

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)
    }
  })
}

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}
   * */
})

allSettled 原理

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

手写 Promise

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

第一步

  • 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)
    }
  }
}

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
  }
}

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

catch 方法

class Promise {
  // ...省略其他
  catch(fn) {
    return this.then(undefined, fn)
  }
}

第三步

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>
  • 如果 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)
  }
}

完整代码

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

测试结果

测试工具

10.jpg

常见问题

输出顺序

第一个

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)

输出结果如下:

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

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

  • 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)
}

可以看到 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())
  }
}

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

红绿灯交替闪烁

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

三个亮灯函数:

function red() {
  console.log('red')
}
function green() {
  console.log('green')
}
function yellow() {
  console.log('yellow')
}

实现

  • 利用递归实现不断重复的效果
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()

实现异步并发控制

/**
 * 异步并发限制
 *
 * @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)
})

输出结果如下:

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']