手写一个promise

896 阅读6分钟

本文主要通过例举promise的用法,来实现一个promise。

状态初始化

promise用法

let p = new Promise((res, rej) => {
   // res('success')
   rej('err')
 })
 console.log(p1); 
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: "success"

promise初始化实现

首先,确定promise用一个类可以实现,promise初始化时,需要知道promise的状态,以及promise的返回值。

image.png

在promise中,存在PromiseState、PromiseResult两属性,分别存放promise的状态和结果。

用户在new Promise时,传入的是一个回调函数,在函数中使用resolve、rejected。

  • 在调用resolve时,将状态修改为fulfilled,并给结果赋值。
  • 在调用reject时,将状态修改为rejected,并给结果赋值。

初始化如下:

class MyPromise {
  constructor(cb) {
    this['[[PromiseState]]'] = 'pending'
    this['[[PromiseResult]]'] = undefined
    this.onResolved = [] // 成功回调集合 两个集合暂未使用
    this.onRejected = [] // 存放失败的回调集合
    cb(this.#resolve.bind(this), this.#reject.bind(this))  // 需要绑定this,否则获取不到
  }
  #resolve(val) {
    this['[[PromiseState]]'] = 'fulfilled'
    this['[[PromiseResult]]'] = val
  }
  #reject(val) {
    this['[[PromiseState]]'] = 'rejected'
    this['[[PromiseResult]]'] = val
  }
}

如果在cb调用不对promise内部私有属性绑定this,运行后就会报错: image.png this输出为undefined,此时获取不到this,所以需要在cb调用时,绑定this。

静态方法

resolve

Promise.resolve() 返回一个解析过的promise对象。

使用方法

console.log(Promise.resolve('success')); 
// Promise{
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: "success"
// }

实现

直接使用resolve静态方法,输出的结果等同于 new Promise((res, rej) => { res('success') })。因此,在调用resolve方法时,需要实例化一个Promise类,在内部调用res。

 static resolve(val) {
    return new MyPromise((res, rej) => {
      res(val)
    })
  }

reject

Promise.reject() 返回一个拒绝的Promise对象。

使用方法

console.log(Promise.reject('err')); 
// Promise{
// [[PromiseState]]: "rejected"
// [[PromiseResult]]: "err"
// }

实现

直接使用resolve静态方法,输出的结果等同于 new Promise((res, rej) => { rej('err') })

 static reject(val) {
    return new MyPromise((res, rej) => {
      rej(val)
    })
  }

allSettled

Promise.allSettled() 接收一个由已经执行完promise(状态为:fulfilled或rejected)的结果组成的对象数组。

allSettled用法

let p2 = new Promise((res, rej) => {
      setTimeout(() => {
        res("p1");
      }, 2000);
    })
    let p3 = new Promise((res, rej) => {
      setTimeout(() => {
        rej("p2 err");
      }, 1000);
    })
    Promise.allSettled([p2, p3]).then(res => {
      console.log(res); // [{status: "fulfilled", value: "p1"},{status: "rejected", reason: "p2 err"}]
    })

实现方法

allSettled接收的是一个对象数组,不管状态如何,结果输出的也是一个数组。在数组循环调用then时,需要考虑两种状态的返回结果,在不同的状态中,分别设置该项中的数据返回格式。例如:{status: "fulfilled", value: "p1"},成功中两个属性分别为status和value。

在实现时,需要设置一个变量,存储返回新结果的长度num。如果num等于接收数组lists长度,则表明已经全部修改统一格式,此时就需要执行下一步回调。

 static allSettled(lists) {
    let resArr = new Array(lists.length)
    let num = 0
    return new MyPromise((resolve) => {
      lists.forEach((item, key) => {
        let obj = {}
        item.then(
          (res) => {
            obj['status'] = 'fulfilled'
            obj['value'] = res
            resArr[key] = obj
            num++
            if (num >= lists.length) {
              resolve(resArr)
            }
          },
          (err) => {
            obj['status'] = 'rejected'
            obj['reason'] = err
            resArr[key] = obj
            num++
            if (num >= lists.length) {
              resolve(resArr)
            }
          },
        )
      })
    })
  }

all

Promise.all 等待所有的都完成,如果all中数组有一个错误,则执行异常。

all用法

let p2 = new Promise((res, rej) => {
      setTimeout(() => {
        res("p1");
      }, 2000);
    })
    let p3 = new Promise((res, rej) => {
      setTimeout(() => {
        res("p2");
      }, 1000);
    })
    Promise.all([p2, p3])
    .then(res => {
      console.log(res);
    })
 // ["p1", "p2"]

all实现

和allSettled类似,只需要考虑成功的情况,但是还是需要判断所有的是否都已执行完成。

  static all(list) {
    return new Promise((resolve, reject) => {
      if (!Array.isArray(list)) {
        return reject(new Error('传入的必须是数组'))
      }
      let res = [],
        l = list.length,
        count = 0
      list.forEach((_, i) => {
        Promise.resolve(_)
          .then((result) => {
            count++
            res[i] = _
            if (count === l) resolve(res)
          })
          .catch((e) => reject(e))
      })
    })
  }

但是真实的all:是所有回调都完成返回,如果其中一个异常,其他的回调也会执行,不过最终返回的状态是reject。

race

Promise.race()谁执行的时间短就执行谁,无论状态如何。

race用法

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('success')
   }, 1000)
 })

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('failed')
  }, 500)
})

Promise.race([p1, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)  // 打开的是 'failed'
})

race实现

在遍历lists时,直接调用then。

  static race(lists) {
    return new MyPromise((resolve, reject) => {
      lists.forEach((item) => {
        item.then(
          (res) => {
            resolve(res)
          },
          (err) => {
            reject(err)
          },
        )
      })
    })
  }

any

Promise.any()返回成功状态中的第一个。

any用法

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('success')
    }, 1000)
  })
  let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('failed')
    }, 500)
  })
  Promise.any([p1, p2]).then((result) => {
    console.log(result)
  }).catch((error) => {
    console.log(error)
  })
 // success

any实现

执行成功状态的第一个回调。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例。

  static any(lists) {
  return new MyPromise((resolve, reject) => {
    let errs = []
    let len = lists.length
    if (len === 0) reject(new AggregateError('All promises were rejected'))
    lists.forEach(item => {
      item.then(res => {
        resolve(res)
      }, err => {
        len--
        errs.push(err)
        if (len === 0) reject(new AggregateError(errs))
      })
    })
  })
}

原型方法

then原型方法

Promise.prototype.then() 最多接收两个回调函数:成功和失败情况下的回调函数。

基本then

基本then用法

   let p = new Promise((res, rej) => {
      res('success');
    })
   p.then(res => {
        console.log(res); // success
    }, err => {
      console.log(err);
    })

基本then的实现

通过上段then的基本用法,可以根据new Promise内执行的res或rej,then中分别对应正确的回调或抛出错误。在then中就可以判断promise的状态来执行不同的回调。

then(onFullfill, onReject) {
    if (this['[[PromiseState]]'] === 'fulfilled') {
      onFullfill(this['[[PromiseResult]]'])
    }
    if (this['[[PromiseState]]'] === 'rejected') {
      onReject(this['[[PromiseResult]]'])
    }
}

状态为fulfilled时,表示成功;为rejected,表示失败。 调用多个then时,根据promise中调用的释放回调用,同时多个then中相应的回调也会触发。

 let p = new MyPromise((res, rej) => {
    res('success')
  });
p.then(res => {
  console.log(res);
}, err => {
  console.log('err', err);
})
p.then((res) => {
 console.log(res, '2');
}, (rej) => {
  console.log('err2');
})
// success
// success 2

then的链式调用

then的链式用法

    let p1 = new Promise((res, rej) => {
      res('success');
    })
    p1.then(res => {
      return new Promise((res, rej) => {
        res('success2')
      })
    }, err => {
      console.log(err);
    }).then(res => {
      console.log('2', res); // 2 success2
    })

then的链式实现

链式的实现,就相当于返回一个promise。根据上一个promise的状态,在then中执行相应状态的回调,依次类推。如果上一个then中返回一个promise,则此时then中的状态为'pending'。

  #resolve(val) {
    if (this['[[PromiseState]]'] !== 'pending') return
    setTimeout(() => {
      this['[[PromiseState]]'] = 'fulfilled'
      this['[[PromiseResult]]'] = val
      // this.onResolvedQueue.forEach((fn) => fn && fn(val))
      let cb;
      while(cb = this.onResolvedQueue.shift()) {
        cb(val)
      }
    })
  }
  #reject(err) {
    if (this['[[PromiseState]]'] !== 'pending') return
    setTimeout(() => {
      this['[[PromiseState]]'] = 'rejected'
      this['[[PromiseResult]]'] = err
      // this.onRejectedQueue.forEach((fn) => fn && fn(err))
      let cb;
      while(cb = this.onRejectedQueue.shift()) {
        cb(err)
      }
    })
  }

 then(onFullfill, onReject) {
    // 返回的是新promise
    return new MyPromise((resolve, reject) => {
      // 成功执行的函数
      let onFullfillFn = (val) => {
        let res = onFullfill && onFullfill(val)
        // 返回一个新的promise 执行下一个回调
        res instanceof MyPromise ? res.then(resolve) : resolve(res)
      }
      // 失败执行的函数
      let rejectFn = (err) => {
        onReject && onReject(err)
        reject(err)
      }
      this.onResolvedQueue.push(onFullfillFn)
      this.onRejectedQueue.push(rejectFn)
    })
  }

then中会接受最多两个的回调函数,在成功的回调中,传入的可能直接是逻辑处理,也可能是需要返回一个新的promise对象,所以在返回时,需要判断返回类型。如果传入的是失败函数,直接执行用户失败的回调。

catch原型方法

Promise.prototype.catch() 捕获rejected情况,并处理。

catch用法

let p = new Promise((res, rej) => {
      rej('err')
})
p.catch((reason) => {
  console.log(reason) // err
})

catch实现

直接执行then的失败回调。

catch(cb) {
    this.then(undefined, cb)
  }

finally原型方法

Promise.prototype.finally() 无论是fulfiled还是rejected,都会执行finally内的回调函数。

finally用法

let p = new Promise((res, rej) => {
      res('success')
    })
    p.then((reason) => {
      console.log(reason)
    }).finally(() => {
      console.log('finally');
    })
// success finally

finally实现

 finally(cb) {
    return this.then(cb, cb)
  }