从零实现Promise

1. 什么是Promsie?

Promise 是异步编程的一种解决方案。用于解决传统回调嵌套的问题(回调地狱),多层的回调嵌套使得代码复杂难维护。它由社区最早提出和实现(jq 1.5版本的deferred就已经有了promise的概念),ES6 将其写进了语言标准。

2. Promise基本使用

2.1 最简单的例子

new Promise((resolve, reject) => {
    console.log(2) // 2,在new Promise的时候会立即执行
    resolve('success')
    reject('error')
}).then(res => {
    console.log(res) //此时会打印出success
}, reason => { console.log(reason) })

new Promise((resolve, reject) => {
    reject('error')
    resolve('success')
}).then(res => { console.log(res) }, reason => { 
    console.log(reason)  //此时会打印出error
})
复制代码

从上面一个简单的例子可知:

  1. Promise是一个类,在new Promise时,需要传入一个executor(执行器函数),这个函数会立即执行
  2. excutor接收两个参数,参数为两个函数,resolve表示成功, reject表示失败
  3. Promise默认状态是等待态:pending。可以从pending转换为resolve或是reject。但是不能从resolve或是reject转变成其他状态
  4. Promise原型上有一个then方法,then方法需要传入两个方法,在Promise对象改变状态的时候调用

2.2 使用Promise处理异步

// 成功的情况
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(100)
    }, 1000)
})

p.then(res => {
    console.log(res) // 100
})


// 失败的情况
let p2 = new Promise((resolve, reject) => {
    // 如果的调用了reject,亦或是executor执行函数执行的时候报错
    // 都会触发then方法里的第二个函数,并把reject传的值,或是报错的信息传进去
    setTimeout(() => {
        reject('error')
    }, 1000)
})

p2.then(res => {
    console.log(res)
}, reason => {
    console.log(reason) // error
})
复制代码

2.3 Promsie:链式调用

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(100)
    }, 1000)
})

p.then(res => {
    console.log(res) // 100
    return 200       // 如果有返回值,那么会传到写一个then方法的回调里
}).then(res => {
    console.log(res) // 200
})
复制代码
  1. 如果一个then方法,return一个普通值,则这个值会传递到下一个then的成功回调中
  2. 如果不是普通值—promise 或是 throw error
  3. 如果是一个promise,会根据这个promise是resolve还是reject,决定下一个then执行成功还是失败的回调
  4. 捕获错误的机制,(默认会找离自己最近的then的失败的回调,找不到就继续向下找)

3. async和await

既然讲到了Promise是用于处理异步以解决回调地狱的问题。那么就顺带提一下async/await, 这是ES7新增的。以往的异步方法无外乎回调函数和Promise。但是async/await建立于Promise之上

  1. async: 修饰函数,最后默认让函数返回一个promise实例
  2. 函数执行报错,实例状态是失败,结果是报错原因,就会走到then方法的失败的回调里
  3. 如果函数正常执行,则实例状态是成功,结果是return后面的值。return的值会走到then方法的成功回调里
  4. 一般都是配合await的「函数中使用await,则必须基于async修饰才可以」
async function fn() {
    return 10;
}
fn().then(result => {
    console.log(result) // 10
})
复制代码

函数中使用await,则必须基于async修饰才可以

  1. await后面一半都是跟着promise实例
    • 如果后面是一个正常的值,如:await 10,会被转成await Promise.resolve(10)
    • 如果后面是一个函数,如 await func,
      • 首先立即执行func函数,接收它的返回值
      • 然后变成await 返回值,最后转成一个promise实例
  2. 本身是异步微任务:把当前上下文中, await下面要执行的代码整体存储到异步的微任务中,当await后面的promise实例状态为成功后,再去执行下面的代码
  3. 如果对失败的promise实例没有做异常的处理,则控制台抛出异常信息「不会影响后续代码执行」。那么await需要基于try catch做异常捕获
function computed() {
    console.log(1)
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(2)
        }, 1000)
    })
}
console.log(3)
async function fn() {
    console.log(4)
    let result = await computed() 
    // 会先执行computed函数
    // 并且等到里面的promise实例状态变为成功后,await下面的代码才会开始执行
    console.log(result)
    console.log(5)
}
fn()
console.log(6)
// 3, 4, 1, 6, 2, 5
复制代码

4. 自己实现一个Promise吧

上面介绍了一些Promise的基本使用,那么就基于Promise A+规范来实现以下对应的功能

  1. Promise类
  2. 处理异步
  3. 链式调用then
  4. Promise.resolve,Promise.reject
  5. Promise.all,Promise.race
  6. catch,finally
function Promise(executor) {
    let self = this
    self.status = 'pending' //Promise默认状态为pending
    self.value = undefined  
    self.reason = undefined
    
    self.onResolveCallbacks = [] //存放所有成功的回调--异步逻辑
    self.onRejectCallbacks = [] //存放所有失败的回调--异步逻辑
    
    function resolve(value) {
        if (self.status === 'pending') { //只能从pending转成其他状态
            self.status = 'resolve'
            self.value = value
            // 当状态发生改变后,执行成功的回调(发布订阅模式)
            self.onResolveCallbacks.forEach(fn => fn())
        }
    }
    
    function reject(reason) {
        if (self.status === 'pending') { //只能从pending转成其他状态
            self.status = 'reject'
            self.reason = reason
            // 当状态发生改变后,执行失败的回调(发布订阅模式)
            self.onRejectCallbacks.forEach(fn => fn())
        }
    }
    
    //传递一个executor:执行器(函数),会立即被调用
    // 防止在new Promise时,还没调用resolve或是reject前就直接抛出异常
    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

//promise直接返回resolve
Promise.resolve = function(value) {
  return new Promise((resolve, reject) => {
    resolve(value)
  })
}
//promise直接返回reject
Promise.reject = function(reason) {
  return new Promise((resolve, reject) => {
    reject(reason)
  })
}

/*
  Promise.all实现思路:
    1.如果参数为空数组,则返回一个已完成状态的promise
    2.如果数组不为空,且数组每项为promise对象,参数中的 promise 都变成完成状态,
    Promise.all 返回的 promise 异步地变为完成。
    3.如果传入的参数中其中一个promise失败,则返回reject。不管其他的是否完成
    4.在任何情况下。Promise.all的完成状态的结果 都是一个数组
*/

Promise.all = function(values = []) {
  return new Promise((resolve, reject) => {
    if (values.length === 0) {
      resolve([])
      return
    }
    let arr = [] //最后的结果
    let index = 0

    function processResult(key, value) {
      index ++
      arr[key] = value
      if (index === values.length) {
        resolve(arr)
      }
    }

    for (let i = 0; i < values.length; i ++) {
      let current = values[i]
      if (current && current.then && typeof current.then === 'function') {
        current.then(y => {
          processResult(i, y)
        }, reject)
        //如果传入的参数中其中一个promise失败,则返回reject。不管其他的是否完成
      } else {
        //如果是普通值,直接完成当前的这个promise
        processResult(i, current)
      }
    }
  })
}

/*
  Promise.race实现思路:
    1.如果参数为空数组,则返回一个已完成状态的promise
    2.如果数组不为空,且数组每项为promise对象,参数中的 只要有一个 promise 变成完成状态,
    Promise.race 就马上返回 已完成promise的值。
    3.如果传入的参数中其中一个promise失败,则直接返回reject
*/
Promise.race = function(values) {
  return new Promise((resolve, reject) => {
    if (values.length === 0) {
      resolve()
      return
    }
    for (let i = 0; i < values.length; i ++) {
      let current = values[i]
      if (current && current.then && typeof current.then === 'function') {
        current.then(y => {
          resolve(y)
        }, reject)
        //如果传入的参数中其中一个promise失败,则返回reject。不管其他的是否完成
      } else {
        //如果是普通值,直接完成当前的这个promise
        resolve(current)
      }
    }

  })
}

//onFulfilled和onRejected都是可选的参数
Promise.prototype.then = function(onFulfilled, onRejected) {
  //防止then参数不写的情况,可以把then结果传到下一个then
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
  onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err}

  const self = this
  //调用then后,需要再继续返回一个全新的promise(为了实现promise的链式调用)
  //因为promise的状态改成resolve或是reject后,就不能再改变了
  //所以应该返回的是一个全新的promise
  //在返回一个全新的promise前,需要拿到当前这个then中的onFulfilled(成功)或是onRejected(失败)的结果
  //判断当前then的执行结果与promise2的关系。执行promise2对应的resolve和reject

  let promise2 = new Promise(function(resolve, reject) {
    if (self.status === 'resolve') {
      setTimeout(() => {
        //这里需要到promise2,为了能正确获取到promise2,需要增加异步
        try {
          const x = onFulfilled(self.value)
          
           //判断当前then方法返回的是否为一个promise
          resolvePromsie(promise2, x, resolve, reject)
        } catch (e) {
          //如果有错误,则抛出错误
          //如果onFulfilled或onRejected抛出一个异常e,
          //promise2 必须被拒绝(rejected)并把e当作原因
          //下面几个try{}catch(){}同理
          reject(e)
        }
      }, 0)
    }

    if (self.status === 'reject') {
      setTimeout(() => {
        try {
          const x = onFulfilled(self.reason)
          
           //判断当前then方法返回的是否为一个promise
          resolvePromsie(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0)
    }

    //订阅异步逻辑,如果是异步,先存起来
    if (self.status === 'pending') {
      self.onResolveCallbacks.push(function() {
        setTimeout(() => {
          try {
            const x = onFulfilled(self.value)
            //判断当前then方法返回的是否为一个promise
            resolvePromsie(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)             
      })
      self.onRejectedCallbacks.push(function() {
        setTimeout(() => {
          try {
            const x = onRejected(self.reason)
            
            //判断当前then方法返回的是否为一个promise
            resolvePromsie(promise2, x, resolve, reject) 
          } catch (e) {
            reject(e)
          } 
        }, 0)
      })
    }
  })

  return promise2 //返回一个全新的promise,这样就可以在then后再继续调用then方法
}

// catch 方法其实很简单,相当于 then 方法的一个简写
Promise.prototype.catch = function(err){
    return this.then(null,err)
}

// 不管Promise最后的状态如何,都要执行一些最后的操作
Promise.prototype.finally = function(onDone){
    return this.then(onDone, onDone)
}


function resolvePromsie(promise2, x, resolve, reject) {
  if (promise2 === x) {
    //如果promise2和x引用同一个对象,则用TypeError作为原因拒绝(reject)
    //如果当前的x也是promise2,则直接调用promise2的失败回调reject
    return reject(new TypeError('promise 循环调用'))
  }

  let called //禁止多次调用resolve或是reject,如果多次被调用,值执行第一个,后面的忽略

  //判断x是不是一个对象或是函数,如果都不是,则为一个常量,如果是常量,直接传到promise2的成功回调resolve
  if ((x !== null && typeof x === 'object') || typeof x === 'function') {
    //x不为null,且x是一个对象或是函数。
    //如果x是个对象或者方法。取到.then -----判断是不是promise,如果取不到then。则抛出异常
    try {
      let then = x.then
      if (typeof then === 'function') {
        //如果then是函数,则表示x是一个promise
        //如果是then是一个方法,直接执行---then接收两个参数
        then.call(x, y => {
          //resolve(y) // 直到y是一个普通值,然后调用resolve
          //但是y可能也是一个promise,所以要递归进行判断, 直到y是一个普通值
          if (called) return
          called = true
          resolvePromsie(promise2, y, resolve, reject)      
        }, r => {
          if (called) return
          called = true
          reject(r)
        })
      } else {
        //如果x下then不是一个函数,则表示x是一个普通对象,直接调用resolve把x返回
        resolve(x)
      }
    } catch (error) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    //如果 x既不是对象也不是函数,也没报错。则直接调用promise2的成功回调resolve
    resolve(x)
  }
}

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享