还是有必要重学一次Promise

前端小卡

共 20044字,需浏览 41分钟

 ·

2021-03-06 23:33

关于Promise的文章那么多,内容或许差不多,那我为什么还要写一篇?两个原因:一是为了让自己更深入学习;二是将自己学习的过程记录下来。下面的内容就是按照Promises/A+规范自己手写一遍代码,包括常用的几个Promise方法。如果读者认为自己手写一个Promise完全没问题,那就没有必要浪费时间;如果感觉掌握的马马虎虎,我倒建议继续往下看看。

tip: 预计看完整片文章3-5分钟

Promise在解决什么问题?

  • 有个词叫“回调地狱”,没错,主要解决的就是这个问题。直接看下面的例子。
// 一般回调写法
api.get('https://test.com/person?name="equicy"', (data) => {
  if (data.code === 0) {
    api.get('https://test.com/class?id='+data.id, (data) => {
      if (data.code === 0) {
        api.get('https://test.com/school?id='+data.id, (data) => {
            console.log(data)
        })
      }
    })
  }
})

// Promise写法
function request(url{
  return new Promise((resolve, reject) => {
    api.get(url, (data) => {
      if (data.code === 0) {
        resolve(data)
      } else {
        reject('出错啦')
      }
    })
  })
}

request('https://test.com/person?name="equicy").then(data => {
  return request('
https://test.com/class?id='+data.id)
}).then(data => {
  return request('https://test.com/school?id='+data.id)
}).then(data => {
  console.log(data)
}).catch(err => {
  console.log(err)
})

Promise是什么?

- Promise的写法有很多,只要满足Promises/A+这个规范,就可以叫做Promise。因为这个规范是英文的,我依照自己认为的重点和自身的理解用中文简单翻译一下「如果看不懂也没有关系,因为这里的每一条,都会在接下来的手写代码中有所体现」

一. 「promise的状态」
  • promise总共有三种状态:pending(待定)、fulfilled(已实现)、rejected(已拒绝)
  • 当前状态为pending时,可转换为fulfilled或者rejected
  • 当前状态为fulfilled时,必须要返回一个值,且状态不可改变
  • 当前状态为rejected时,必须要返回一个原因,且状态不可改变

二. 「then方法」

  • 一个promise必须要提供一个then的方法来接收当前的返回值

  • then可以有两个可选参数(onFulfilled, onRejected)

  • onFulfilled若存在,则为函数形式;若为普通值,则将其转换为函数(_=>_)

  • onRejected若存在,则为函数形式;若为普通值,则将其转换为抛出异常的函数(err=>{throw err})

  • 在当前执行上下文中不能执行onFulfilled和onRejected,换句话说,需要异步执行这两个函数,例如在宏任务setTimeout、setImmediate或者微任务mutationObserver中执行。

  • 对于then,可被多次调用

    • 当fulfilled后,所有onFulfilled回调都必须按其注册顺序执行

    • 当rejected后,所有onRejected回调都必须按其注册顺序执行

  • then必须返回的是一个promise

三. promise的处理过程「在接下来的代码表现为函数resolvePromise」

  • promise的处理过程需要一个promise2和一个值x,x为回调函数onFulfilled或onRejected的执行结果。promise2则为返回的新promise,我们暂且表示为[[Resolve]](promise, x)

  • 运行[[Resolve]](promise, x)需要满足的条件

  • 如果promise2和x指向同一个对象(会造成死循环),则抛出类型错误

  • 如果x不是一个函数或者对象,则以x为值执行promise, --resolve(x)

  • 如果x是一个函数或者对象,将x.then赋予新的变量then。let then=x.then

  • 将x作为this,执行then。

  • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y),此时递归执行,解决多次调用then的情况

  • 如果 rejectPromise 以拒绝原因r为参数被调用,则以拒绝原因r拒绝 promise

遵循规范自己手写一个Promise

- 接下来就依照上面提到的规范来自己实现一个promise

// 首先定义三种状态为三个常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

// 定义一个promise类
class Promise {
  // 该构造函数接收一个执行器(函数executor),executor需要在实例化的时候立即执行。
  constructor(executor) {
    this.status = PENDING // 默认状态为待定
    this.value = undefined // 成功的返回值
    this.reason = undefined // 失败的原因

    let resolve = (val) => {
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = val
      }
    }

    let reject = (error) => {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = error
      }
    }

    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
  
  then(onFulfilled, onRejected) {
    // 状态为fulfilled,执行onFulfilled,传入成功的值
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    // 状态为rejected,执行onRejected,传入失败的原因
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
  }

- 看着似乎没什么问题,但是如果再加点料。像这样,就会有问题

const promise = new Promise((resolve, reject) => {
  setTimeout(() =>{
     resolve(1)
   })
})

promise.then((value) =>{
  console.log(value) // '',没有输出任何结果
})

- 考虑异步的情况

  • 接下来就需要解决这种情况,也就是考虑异步的情况。也就是用两个数组(onResolveCallbacks,onRejectCallbacks)来收集回调,当执行到下一个tick的时候(事件循环机制),依次触发收集的回调函数(发布订阅)。

class Promise {
  // 该构造函数接收一个执行器(函数executor),executor需要在实例化的时候立即执行。
  constructor(executor) {
    this.status = PENDING // 默认状态为待定
    this.value = undefined // 成功的返回值
    this.reason = undefined // 失败的原因

    this.onResolveCallbacks = [] // 成功的回调函数集合
    this.onRejectCallbacks = [] // 失败的回调函数集合

    let resolve = (val) => {
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = val
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    }
    let reject = (error) => {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = error
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
  
  then(onFulfilled, onRejected) {
    // 状态为fulfilled,执行onFulfilled,传入成功的值
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    // 状态为rejected,执行onRejected,传入失败的原因
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
    if (this.status === PENDING) {
        /*
        * 异步并且多次调用的情况
        * p = new Promise((resolve, reject) => {
        *    setTimeout(() =>{
        *      resolve(1)
        *    })
        * })
        * p.then()
        * p.then()
        */

        this.onResolveCallbacks.push(() => {
          onFulfilled(this.value)
        })

        this.onRejectCallbacks.push(() => {
          onRejected(this.reason)
        })
      }
  }

- 解决链式调用的问题

  • 似乎promise允许这样使用,promise.then().then().then()

  • 所以规定then函数返回的仍然是一个promise,我们称为promise2

  • 规定then的两个参数如果不是函数则需要将其转为函数形式

  • 第一个then返回的值中叫做x,我们用函数resolvePromise来判断x

then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : _ => _
    onRejected = typeof onRejected == 'function' ? onRejected : err => { throw err }

    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      }

      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      }

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

        this.onRejectCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })

    return promise2
  }

- 接下来就是对函数resolvePromise的实现

  • 如果promise2和x指向同一个对象(会造成死循环),则抛出类型错误

  • 如果x不是一个函数或者对象,则以x为值执行promise, --resolve(x)

  • 如果x是一个函数或者对象,将x.then赋予新的变量then。let then=x.then

  • 将x作为this,执行then。

  • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y),此时递归执行,解决多次调用then的情况

  • 如果 rejectPromise 以拒绝原因r为参数被调用,则以拒绝原因r拒绝 promise

  • 下面就将这些规范应用到下面的代码中

function resolvePromise(promise2, x, resolve, reject{
  // 如果promise2和x指向同一个对象(会造成死循环),则抛出类型错误
  if (promise2 === x) {
    return reject(new TypeError('A promise cannot be resolved with itself')) // 禁止循环引用
  }

  let called;
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    try {
      let then = x.then
      if (typeof then === 'function') {
      // 将x作为this,执行then。
        then.call(x, y => {
          if(called) return;
          called = true;
          resolvePromise(promise2, y, resolve, reject)
        }, r => {
          if(called) return;
          called = true;
          reject(r)
        })
      } else {
        resolve(x)
      }

    } catch (error) {
      if(called) return;
      called = true;
      reject(error)
    }
  } else {
    resolve(x)
  }
}

- 到这里算是大功告成,下面再实现几个常用的方法

手写Promise的几个常用方法

// promise.resolve 直接返回成功态
Promise.resolve = function(val){
  return new Promise((resolve,reject)=>{
    resolve(val)
  });
}
//promise.reject方法 直接返回失败态
Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  });
}
// promise.all 所有成功才能返回成功的数组,有一个失败,则返回失败
Promise.all = function(promises{
  return new Promise(function(resolve, reject{
    var result = []
    var count = 0
    var promiseNum = promises.length
    if (promises.length === 0) resolve([])

    for (let i = 0; i < promiseNum; i++) {
      Promise.resolve(promises[i]).then(function(val{
        count++
        result[i] = val
        // 这样可以保证只有全部请求完成,才能返回所有的结果
        if (count === promiseNum) resolve(result)
      }, function(error){
        reject(error)
      })
    }
  })
}

// promise.race 一旦迭代器中的某个promise解决或拒绝,返回的promise就会解决或拒绝。
Promise.race = function(promises{
  return new Promise(function(resolve, reject){
    for (var i; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(function(data){
        resolve(data)
      }, function(error{
        reject(error)
      })
    }
  })
}

// promise.allSettled 返回所有的失败和成功的状态,并含有状态的对象 
// allSettled 结果示例
// { status: 'fulfilled', data: 3 },
// { status: 'rejected', err: 'foo' }

Promise.allSettleds = function(promises{
  var res = []
  var count = 0
  return new Promise((resolve, reject) => {
      for (let i=0; i<promises.length; i++) {
          promises[i].then((data)=> {
              res[i] = data
              if (count === promises.length-1) {
                resolve(res)
              }
              count++
          }).catch(err=>{
              res[i] = err
              if (count === promises.length-1) {
                resolve(res)
              }
              count++
          })
      }
  })
}

欢迎批评指正或者点赞


浏览 22
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报