手写promise全家桶

关于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++
          })
      }
  })
}

欢迎批评指正或者点赞

同时也欢迎关注公众号「前端小卡」进行交流

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容