lovelmh13/One-question-a-day

分步骤实现 A+ 规范的 Promise

lovelmh13 opened this issue · 0 comments

1. 初级版

function MyPromise(fn) {
  this.status = 'pending'
  this.val = ''
  this.error = ''
  this.fulfilledList = []
  this.rejectedList = []
  const resolve = (params) => {
    // 注意 this ,如果不暂存 this,就要用箭头函数
    if (this.status === 'pending') {
      this.status = 'fulfilled'
      this.val = params
    }
  }

  const reject = (error) => {
    if (this.status === 'pending') {
      this.status = 'rejected'
      this.error = error
    }
  }

  fn(resolve, reject)
}

MyPromise.prototype.then = function (fulfilledFn, rejectedFn) {
  if (this.status === 'fulfilled') {
    fulfilledFn(this.val)
  }
  if (this.status === 'rejected') {
    rejectedFn(this.error)
  }
}

new MyPromise((resolve, reject) => {
  resolve(1)
}).then(
  (data) => {
    console.log(data)
  },
  (err) => {
    console.log(err)
  }
)

2. 支持异步版

function MyPromise(fn) {
  this.status = 'pending'
  this.val = ''
  this.error = ''
  this.fulfilledList = []
  this.rejectedList = []
  const resolve = (params) => {
    // 注意 this ,如果不暂存 this,就要用箭头函数
    if (this.status === 'pending') {
      this.status = 'fulfilled'
      this.val = params
      this.fulfilledList.forEach((fn) => {
        fn(this.val)
      })
    }
  }

  const reject = (error) => {
    if (this.status === 'pending') {
      this.status = 'rejected'
      this.error = error
      this.rejectedList.forEach((fn) => {
        fn(this.error)
      })
    }
  }

  fn(resolve, reject)
}

MyPromise.prototype.then = function (fulfilledFn, rejectedFn) {
  if (this.status === 'fulfilled') {
    fulfilledFn(this.val)
  }
  if (this.status === 'rejected') {
    rejectedFn(this.error)
  }
  if (this.status === 'pending') {
    this.fulfilledList.push(fulfilledFn)
    this.rejectedList.push(rejectedFn)
  }
}

new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
}).then(
  (data) => {
    console.log(data)
  },
  (err) => {
    console.log(err)
  }
)

3. 链式调用版

不支持 then 里再返回 Promise 的 版本

    function MyPromise(fn) {
        this.status = 'pending'
        this.val = ''
        this.error = ''
        this.fulfilledList = []
        this.rejectedList = []
        const resolve = (params) => {
          // 注意 this ,如果不暂存 this,就要用箭头函数
          if (this.status === 'pending') {
            this.status = 'fulfilled'
            this.val = params
            this.fulfilledList.forEach((fn) => {
              fn(this.val)
            })
          }
        }

        const reject = (error) => {
          if (this.status === 'pending') {
            this.status = 'rejected'
            this.error = error
            this.rejectedList.forEach((fn) => {
              fn(this.error)
            })
          }
        }

        fn(resolve, reject)
      }

      MyPromise.prototype.then = function (fulfilledFn, rejectedFn) {
        return new MyPromise((resolve, reject) => {
          // 成功的回调
          const fulfilledCb = () => {
            queueMicrotask(() => {
              // 别忘了 then 是微任务, queueMicrotask 在 window 对象上才有
              try {
                const res = fulfilledFn(this.val)
                resolve(res)
              } catch (err) {
                reject(err)
              }
            })
          }
          // 错误的回调
          const rejectedCb = () => {
            queueMicrotask(() => {
              try {
                const res = rejectedFn(this.error) // 注意这里,虽然是 rejected 状态,但是回调返回的值,如果不是错误,依然会走 then 的 MyPromise 的
                resolve(res)
              } catch (error) {
                reject(error)
              }
            })
          }

          if (this.status === 'fulfilled') {
            fulfilledCb()
          }
          if (this.status === 'rejected') {
            rejectedCb()
          }
          if (this.status === 'pending') {
            this.fulfilledList.push(fulfilledCb)
            this.rejectedList.push(rejectedCb)
          }
        })
      }

      new MyPromise((resolve, reject) => {
        setTimeout(() => {
          resolve(1)
        }, 1000)
      })
        .then(
          (data) => {
            console.log(data)
            return '第一个完成'
          },
          (err) => {
            console.log(err)
          }
        )
        .then((data) => {
          console.log(data)
        })

      console.log('同步函数')

支持 then 里返回 Promise 的版本

function MyPromise(fn) {
  this.status = 'pending'
  this.val = ''
  this.error = ''
  this.fulfilledList = []
  this.rejectedList = []
  const resolve = (value) => {
    if (value instanceof MyPromise) {
      return value.then(resolve, reject) // 细节,适用于 then 里又 return Promise.resolve() 的情况
    }
    // 注意 this ,如果不暂存 this,就要用箭头函数
    if (this.status === 'pending') {
      this.status = 'fulfilled'
      this.val = value
      this.fulfilledList.forEach((fn) => {
        fn(this.val)
      })
    }
  }
  const reject = (error) => {
    if (this.status === 'pending') {
      this.status = 'rejected'
      this.error = error
      this.rejectedList.forEach((fn) => {
        fn(this.error)
      })
    }
  }
  fn(resolve, reject)
}
MyPromise.prototype.then = function (fulfilledFn, rejectedFn) {
  const thenPromise = new MyPromise((resolve, reject) => {
    // 成功的回调
    const fulfilledCb = () => {
      queueMicrotask(() => {
        // 别忘了 then 是微任务, queueMicrotask 在 window 对象上才有
        try {
          const res = fulfilledFn(this.val)
          resolvePromise(thenPromise, res, resolve, reject)
        } catch (err) {
          reject(err)
        }
      })
    }
    // 错误的回调
    const rejectedCb = () => {
      queueMicrotask(() => {
        try {
          const res = rejectedFn(this.error) // 注意这里,虽然是 rejected 状态,但是回调返回的值,如果不是错误,依然会走 then 的 MyPromise 的
          resolvePromise(thenPromise, res, resolve, reject)
        } catch (error) {
          reject(error)
        }
      })
    }
    if (this.status === 'fulfilled') {
      fulfilledCb()
    }
    if (this.status === 'rejected') {
      rejectedCb()
    }
    if (this.status === 'pending') {
      this.fulfilledList.push(fulfilledCb)
      this.rejectedList.push(rejectedCb)
    }
  })
  return thenPromise
}
// 处理当 then 里又 return Promise 的情况
const resolvePromise = (thenPromise, result, resolve, reject) => {
  if (thenPromise === result) {
    return reject(new TypeError('error due to circular reference')) // 死循环
  }
  let called // 防止多次调用(这个变量其实不知道是为什么需要的)
  if (
    result !== null &&
    (typeof result === 'object' || typeof result === 'function')
  ) {
    try {
      const thenable = result.then // 鸭子辨型,如果有 then 属性,就会被认为是 Promise
      if (typeof thenable === 'function') {
        thenable.call(
          result,
          (data) => {
            if (called) {
              return
            }
            called = true
            resolvePromise(thenPromise, data, resolve, reject) // 返回的可能是一个 Promise 实例或者是一个普通值,所以就递归
          },
          (error) => {
            if (called) {
              return
            }
            called = true
            reject(error)
          }
        )
      } else {
        resolve(result)
      }
    } catch (error) {
      if (called) {
        return
      }
      called = true // 防止调用失败后又调用成功
      reject(error)
    }
  } else {
    // 普通的值, 或者没有
    resolve(result)
  }
}
// -------------------- test --------------------
// -------------------- 死循环的例子 --------------------
// const promise = new MyPromise((resolve, reject) => {
//   setTimeout(() => {
//     resolve('lucas')
//   }, 2000)
// })
// promise
//   .then(
//     (onfulfilled = (data) => {
//       console.log(data)
//       return onfulfilled(data)
//     })
//   )
//   .then((data) => {
//     console.log(data)
//   })
// -------------------- 需要递归的例子 --------------------
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 1000)
})
promise
  .then((data) => {
    console.log(data)
    return new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve(`${data} next then`)
      }, 2000)
    }).then((data) => {
      return new MyPromise((resolve, reject) => {
        setTimeout(() => {
          resolve(`${data} next then`)
        }, 2000)
      })
    })
  })
  .then((data) => {
    console.log(data)
  })

4. 支持 Promise 穿透的版本

什么是 Promise穿透:

// -------------------- 穿透的例子 --------------------
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 2000)
})
promise.then(null).then((data) => {
  console.log(data)
})
/**
 * 这段代码将会在 2 秒后输出:lucas。这就是 Promise 穿透现象:
 * 给 .then() 函数传递非函数值作为其参数时,实际上会被解析成 .then(null),这时候的表现应该是:
 * 上一个 promise 对象的结果进行“穿透”,
 * 如果在后面链式调用仍存在第二个 .then() 函数时,将会获取被穿透下来的结果。
 **/
function MyPromise(fn) {
  this.status = 'pending'
  this.val = ''
  this.error = ''
  this.fulfilledList = []
  this.rejectedList = []
  const resolve = (value) => {
    if (value instanceof MyPromise) {
      return value.then(resolve, reject) // 细节,适用于 then 里又 return Promise.resolve() 的情况
    }
    // 注意 this ,如果不暂存 this,就要用箭头函数
    if (this.status === 'pending') {
      this.status = 'fulfilled'
      this.val = value
      this.fulfilledList.forEach((fn) => {
        fn(this.val)
      })
    }
  }
  const reject = (error) => {
    if (this.status === 'pending') {
      this.status = 'rejected'
      this.error = error
      this.rejectedList.forEach((fn) => {
        fn(this.error)
      })
    }
  }
  fn(resolve, reject)
}
MyPromise.prototype.then = function (fulfilledFn, rejectedFn) {
  // 解决 fulfilledFn, rejectedFn 没有传值的问题(Promise 穿透),给一个默认值,就可以继续往下执行了
  fulfilledFn =
    typeof fulfilledFn === 'function' ? fulfilledFn : (data) => data
  rejectedFn =
    typeof rejectedFn === 'function'
      ? rejectedFn
      : (error) => {
          throw error
        }
  const thenPromise = new MyPromise((resolve, reject) => {
    // 成功的回调
    const fulfilledCb = () => {
      queueMicrotask(() => {
        // 别忘了 then 是微任务, queueMicrotask 在 window 对象上才有
        try {
          const res = fulfilledFn(this.val)
          resolvePromise(thenPromise, res, resolve, reject)
        } catch (err) {
          reject(err)
        }
      })
    }
    // 错误的回调
    const rejectedCb = () => {
      queueMicrotask(() => {
        try {
          const res = rejectedFn(this.error) // 注意这里,虽然是 rejected 状态,但是回调返回的值,如
          resolvePromise(thenPromise, res, resolve, reject)
        } catch (error) {
          reject(error)
        }
      })
    }
    if (this.status === 'fulfilled') {
      fulfilledCb()
    }
    if (this.status === 'rejected') {
      rejectedCb()
    }
    if (this.status === 'pending') {
      this.fulfilledList.push(fulfilledCb)
      this.rejectedList.push(rejectedCb)
    }
  })
  return thenPromise
}
// 处理当 then 里又 return Promise 的情况
const resolvePromise = (thenPromise, result, resolve, reject) => {
  if (thenPromise === result) {
    return reject(new TypeError('error due to circular reference')) // 死循环
  }
  let called // 防止多次调用(这个变量其实不知道是为什么需要的)
  if (
    result !== null &&
    (typeof result === 'object' || typeof result === 'function')
  ) {
    try {
      const thenable = result.then // 鸭子辨型,如果有 then 属性,就会被认为是 Promise
      if (typeof thenable === 'function') {
        thenable.call(
          result,
          (data) => {
            if (called) {
              return
            }
            called = true
            resolvePromise(thenPromise, data, resolve, reject) // 返回的可能是一个 Promise 实例或者
          },
          (error) => {
            if (called) {
              return
            }
            called = true
            reject(error)
          }
        )
      } else {
        resolve(result)
      }
    } catch (error) {
      if (called) {
        return
      }
      called = true // 防止调用失败后又调用成功
      reject(error)
    }
  } else {
    // 普通的值, 或者没有
    resolve(result)
  }
}

用 promises-aplus-tests 测试 A+

注意,如果用这个测试,需要把代码里的 queueMicrotask 改成 setTimeout,因为 queueMicrotask 是在 window 上才有的

先全部安装 promises-aplus-tests

然后在 MyPromise 的文件后面加上:

MyPromise.defer = MyPromise.deferred = function () {
  let dfd = {}
  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
module.exports = MyPromise

最后在命令行执行 promises-aplus-tests ./A+test.js

5. Promise 的各种 API 实现

// -------------------- catch  finally 都是在原型上的方法,都是返回一个 then --------------------
MyPromise.prototype.catch = function (cb) {
  return this.then(null, cb)
}
MyPromise.prototype.finally = function (cb) {
  return this.then(
    (value) => {
      return MyPromise.resolve(cb()).then(() => value)
    },
    (error) => {
      return MyPromise.resolve(cb()).then(() => {
        throw error
      })
    }
  )
}
// -------------------- resolve reject all any race 都是在实例上的方法,都是返回一个 MyPromise 实例 --------------------
MyPromise.resolve = function (val) {
  return new MyPromise((resolve) => {
    resolve(val)
  })
}
MyPromise.reject = function (err) {
  return new MyPromise((resolve, reject) => {
    reject(err)
  })
}
// ------------------- all any race 都是在实例上的方法,都是返回一个 MyPromise 实例,且都在返回的实例执行循环,使用MyPromise.resolve().then() 返回结果 --------------------
MyPromise.all = function (iterable) {
  if (!iterable[Symbol.iterator]) {
    return new TypeError(`${iterable} is not iterable`)
  }
  const arr = Array.from(iterable)
  const res = []
  return new MyPromise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      // 保证顺序
      const curr = arr[i]
      MyPromise.resolve(curr).then(
        (val) => {
          res[i] = val
          if (i === arr.length - 1) {
            resolve(res)
          }
        },
        (error) => {
          reject(error)
        }
      )
    }
  })
}
MyPromise.any = function (iterable) {
  if (!iterable[Symbol.iterator]) {
    return new TypeError(`${iterable} is not iterable`)
  }
  const arr = Array.from(iterable)
  const res = []
  return new MyPromise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      const curr = arr[i]
      MyPromise.resolve(curr).then(
        (val) => {
          resolve(val)
        },
        (error) => {
          res[i] = error
          if (i === arr.length - 1) {
            reject(res)
          }
        }
      )
    }
  })
}
// 返回第一个已经结束的状态
MyPromise.race = function (iterable) {
  if (!iterable[Symbol.iterator]) {
    return new TypeError(`${iterable} is not iterable`)
  }
  const arr = Array.from(iterable)
  return new MyPromise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      const curr = arr[i]
      MyPromise.resolve(curr).then(resolve, reject)
    }
  })
}
// -------------------- test --------------------
// -------------------- all --------------------
// const p1 = new MyPromise((resolve, reject) => {
//   resolve(1)
// })
// const p2 = new MyPromise((resolve, reject) => {
//   resolve(2)
// })
// const p3 = '123'
// const p4 = MyPromise.resolve(4)
// MyPromise.all([p1, p2, p3, p4]).then(
//   (data) => {
//     console.log(data)
//   },
//   (error) => {
//     console.log(error)
//   }
// )
// -------------------- any --------------------
// const pErr = new MyPromise((resolve, reject) => {
//   reject('总是失败')
// })
// const pSlow = new MyPromise((resolve, reject) => {
//   setTimeout(resolve, 500, '最终完成')
// })
// const pFast = new MyPromise((resolve, reject) => {
//   setTimeout(resolve, 100, '很快完成')
// })
// MyPromise.any([pErr, pSlow, pFast]).then((value) => {
//   console.log(value)
// })
// 期望输出: "很快完成"
// const pErr = new MyPromise((resolve, reject) => {
//   reject('总是失败')
// })
// MyPromise.any([pErr]).then(
//   (val) => {
//     console.log('val: ', val)
//   },
//   (err) => {
//     console.log(err)
//   }
// )
// -------------------- race --------------------
var p1 = new MyPromise(function (resolve, reject) {
  setTimeout(resolve, 500, 'one')
})
var p2 = new MyPromise(function (resolve, reject) {
  setTimeout(reject, 100, 'two')
})
MyPromise.race([p1, p2]).then(
  function (value) {
    console.log('value: ', value) // "two"
  },
  function (err) {
    console.log('err: ', err)
  }
)