由浅入深,从掌握Promise的基本使用到手写Promise

127 阅读9分钟

前言

在E6之前,对于一些异步任务的处理始终没有很好的方案可以解决,处理异步的方案可谓是十分混乱,在业务需求下异步请求的套用,就形成了回调地狱,严重影响代码的阅读性。而Promise的出现,给我们统一了规范,解决了之前处理异步任务的许多痛点,并且它友好的使用方式,使之成为了JavaScript一大重点,同时也是面试的高频问点,下面就一起来全面认识一下Promise吧。

1.什么是Promise?

如果我们想在一个异步请求之后,拿到请求的结果,在ES6之前我们可以怎么做呢?

比如,给定一个请求地址,希望拿到它请求成功或者失败的结果:

  • 可以通过分别设置成功和失败的两个回调
  • 当请求成功后调用成功的回调,将成功的结果传递过去;
  • 当请求失败后调用失败的回调,将失败的结果传递过去;
function request(url, successCb, failCb) {
  setTimeout(function() {
    if (url === '/aaa/bbb') { // 请求成功
      let res = [1, 2, 3]
      successCb(res)
    } else { // 请求失败
      let err = 'err message'
      failCb(err)
    }
  })
}

// 调用方式,从回调中拿结果
request('/aaa/bbb', function(res) {
  console.log(res)
}, function(err) {
  console.log(err)
})

将上面的情况使用Promise来实现一下:

  • Promise是一个类,通过new调用,可以给予调用者一个承诺;
  • 通过new创建Promise对象时,需要传入一个回调函数,这个回调函数称之为executor,executor接收两个参数resolve和reject
  • 传入的回调会被立即执行,当调用resolve函数时,也就是通过then调用,会去执行Promise对象的then方法中传入的成功回调;
  • 当调用reject函数时,也就是通过catch调用,会去执行Promise对象的then方法中传入的失败回调函数,并且请求后的结果可以通过参数传递过去;
    function request(url) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (url === '/aaa/bbb') {
            let res = [1, 2, 3]
            resolve(res) // 请求成功调用resolve
          } else {
            let err = 'err message'
            reject(err) // 请求失败调用reject
          }
        })
      })
    }

    const p = request('/aaa/bbb')

promise.then(res => {
      console.log("成功的回调")
    }).catch(err => {
      console.log("失败的回调")
    })
//简写为

promise.then(res => {
              console.log("成功的回调")
            }, err => {
              console.log("失败的回调") 
            })

2.Promise的三种状态

为什么Promise能够将请求的结果准确的传递到then中的回调函数中,因为Promise其核心就用三种状态来进行管控。

  • 待定状态(pending) :Promise的初始状态;
  • 已兑现(resolved、fulfilled) :操作成功,如执行resolve时就变为该状态;
  • 已拒绝(rejected) :操作失败,如执行reject时就变为该状态;

通过上面的案例,可以在浏览器中查看Promise分别在执行resolve和reject后的打印结果和Promise当时处于的状态:

  • resolve和reject都没执行:

  • 执行resolve,请求成功:

注意,多次调用reslove,只会执行第一次调用,reject同理

  • 执行reject,请求失败:

注意:在后续的对Promise的讲述过程中,都需要带着Promise的状态去理解。

3.executor

executor是在创建Promise是需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数,分别就是resolve和reject。

new Promise((resolve, reject) => {
  console.log('我是executor中的代码,我会被立即执行~')
})

通常我们会在executor中确定Promise的状态,而且状态一旦被确定下来,Promise的状态就会被锁死,即Promise的状态一旦修改,就不能再次更改了

  • 当调用resolve,如果resolve传入的值不是一个Promise(即传入的值为一个普通值),Promise的状态就会立即变成fulfilled;
  • 但是,如果在resolve后接着调用reject,是不会有任何的效果的,因为reject已经无法改变Promise的结果了;

4.resolve的参数

上面聊到了resolve需要传入一个普通值,Promise的状态才会被立即锁定为fulfilled,那么如果传递的不是普通值呢?一般resolve传递以下三类值,会有不同的表现效果。

  • 传值一:resolve传入一个普通值或普通对象,那么这个值会作为then中第一个回调的参数;

    const p = new Promise((resolve, reject) => {
      resolve(123)
    })
    
    p.then(res => {
      console.log(res) // 123
    })
    
  • 传值二:resolve传入一个Promise,那么这个传入的Promise会决定原来Promise的状态;

    • 传入的Promise调用的是resolve;

      **

      const newP = new Promise((resolve, reject) => {
        resolve(123)
      })
      
      const p = new Promise((resolve, reject) => {
        resolve(newP)
      })
      
      p.then(res => {
        console.log(res) // 123
      }, err => {
        console.log(err)
      })
      
    • 传入的Promise调用的是reject;

      **

      const newP = new Promise((resolve, reject) => {
        reject('err message')
      })
      
      const p = new Promise((resolve, reject) => {
        resolve(newP)
      })
      
      p.then(res => {
        console.log(res)
      }, err => {
        console.log(err) // err message
      })
      
  • 传值三:resolve传入一个特殊对象,该对象中实现了then方法(thenable对象),那么Promise的状态就是对象中then方法执行后的结果来决定的;

    • then中执行了resolve;

      **

      const obj = {
        then: function(resolve, reject) {
          resolve(123)
        }
      }
      
      const p = new Promise((resolve, reject) => {
        resolve(obj)
      })
      
      p.then(res => {
        console.log(res) // 123
      }, err => {
        console.log(err)
      })
      
    • then中执行了reject;

      **

      const obj = {
        then: function(resolve, reject) {
          reject('err message')
        }
      }
      
      const p = new Promise((resolve, reject) => {
        resolve(obj)
      })
      
      p.then(res => {
        console.log(res)
      }, err => {
        console.log(err) // err message
      })
      

5.Promise相关实例方法

Promise的实例方法,就是可以通过其实例对象进行调用的方法。

5.1.then方法

then方法是Promise实例对象上的一个方法:Promise.prototype.then

(1)then方法接收两个参数

  • 状态变成fulfilled的回调函数;
  • 状态变成rejected的回调函数;
promise.then(res => {
  console.log('状态变成fulfilled回调')
}, err => {
  console.log('状态变成rejected回调')
})

(2)then方法多次调用

  • 一个Promise的then方法是可以被多次调用的,每次调用都可以传入对应的fulfilled回调;
  • 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;
  • 反之,当Promise的状态变成rejected,所有then中传入的rejected回调都会被执行;
const p = new Promise((resolve, reject) => {
  resolve('aaa')
})

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

(3)then方法中的返回值

then调用本身是有返回值的,并且它的返回值是一个新的Promise,所以then可以进行链式调用,链式中的then是在等待这个新的Promise有决议之后执行,但是then方法调用的返回值的状态是什么呢?主要是由其返回值决定的。

  • 当then方法中的回调在执行时处于pending状态;

  • 当then方法中的回调返回一个结果时处于fulfilled状态,并且会将结果作为resolve的参数;

    • 返回一个普通的值:这个普通的值会被作为一个新Promise的resolve中的值 p.then(res => { return 123 // 相当于: /* return new Promise((resolve, reject) => { resolve(123) }) */ }).then(res => { console.log(res) // 123 })

    • 返回一个实现了then方法的对象(thenable):

      p.then(res => {
        const obj = {
          then: function(resolve, reject) {
            resolve('abc')
          }
        }
        return obj
        // 相当于:
        /*
          return new Promise((resolve, reject) => {
            resolve(obj.then)
          })
        */
      }).then(res => {
        console.log(res) // abc
      })
      
    • 返回一个Promise:

      p.then(res => {
        const newP = new Promise((resolve, reject) => {
          resolve(123)
        })
        return newP
        // 相当于:
        /*
          const newP = new Promise((resolve, reject) => {
            resolve(123)
          })
          return new Promise((resolve, reject) => {
            resolve(newP)
          })
        */
      }).then(res => {
        console.log(res) // 123
      })
      
  • then方法执行时抛出一个异常,就处于rejected状态,同样,Promise的executor在执行的时候抛出异常,Promise对应的状态也会变成rejected,会执行捕获错误的catch

    const p = new Promise((resolve, reject) => {
      throw new Error('err message')
    })
    
    p.then(res => {
      console.log(res)
    }, err => {
      console.log(err) // Error: err message
      return new Error('then err message')
    }).then(res => {
      console.log(res)
    }, err => {
      console.log(err) // Error: then err message
    })
    

5.2.catch方法

catch方法是Promise实例对象上的一个方法:Promise.prototype.catch

(1)catch方法可多次调用

  • 一个Promise的catch方法也是可以被多次调用的,每次调用都可以传入对应的reject回调;
  • 当Promise的状态变成rejected的时候,这些回调就都会执行;
  • catch方法的效果和then方法的第二个回调函数差不多,可用于替代then方法的第二个回调;
const p = new Promise((resolve, reject) => {
  reject('err message')
})

p.catch(err => {
  console.log(err) // err message
})
p.catch(err => {
  console.log(err) // err message
})
p.catch(err => {
  console.log(err) // err message
})

(2)catch方法的返回值

  • catch方法的执行返回的也是一个Promise对象,使用catch后面可以继续调用then方法或者catch方法;

  • 如果在catch后面调用then方法,会进入到then方法的fulfilled回调函数中,因为catch返回的Promise默认是fulfilled

        p.catch(err => {
          return 'catch return value'
        }).then(res => {
          console.log(res) // catch return value
        })
  • 如果catch后续又调用了catch,那么可以抛出一个异常,就会进入后面的catch回调中;
        p.catch(err => {
          throw new Error('catch err message')
        }).catch(err => {
          console.log(err) // Error: catch err message
        })

(3)catch的作用

  • catch主要是用于捕获异常的,当executor抛出异常是,可以通过catch进行处理;

  • 注意:当Promise的executor执行reject或者抛出异常,后续必须要有捕获异常的处理,如下代码,虽然都调用了then方法,接着后续又调用了catch方法,但是then和catch是两次独立的调用,两次调用并没有联系,所以就被认定为没有处理异常。

    const p = new Promise((resolve, reject) => {
      reject('err message')
    })
    
    p.then(res => {
      console.log(res)
    })
    
    p.catch(err => {
      console.log(err)
    })
    

  • 正确处理的方法为:

        // 方法一:
        p.then(res => {
          console.log(res)
        }).catch(err => {
          console.log(err)
        })

        // 方法二:
        p.then(res => {
          console.log(res)
        }, err => {
          console.log(err)
        })

5.3.finally方法

finally方法是Promise实例对象上的一个方法:Promise.prototype.finally

  • finally是在ES9中新增的,无论Promise的状态变成fulfilled还是rejected,最终都会执行finally中的回调
  • 注意finally是不接收参数的,因为它必定执行;
const p = new Promise((resolve, reject) => {
  resolve(123)
})

p.then(res => {
  console.log(res) // 123
}).catch(err => {
  console.log(err)
}).finally(() => {
  console.log('finally code') // finally code
})

6.Promise相关类方法

Promise的类方法,就是直接通过Promise进行调用。

6.1.resolve方法

resolve方法具体有什么用呢?当我们希望将一个值转成Promise来使用,就可以通过直接调用resolve方法来实现,其效果就相当于在new一个Promise时在executor中执行了resolve方法。

resolve传入的参数类型:

  • 参数为一个普通的值;

    const p = Promise.resolve('aaaa')
    // 相当于:
    /*
      const p = new Promise((resolve, reject) => {
        resolve('aaaa')
      })
    */
    p.then(res => {
        console.log(res) // aaaa
    })
    
    console.log(p)
    

  • 参数为一个实现了then方法的对象;

    const p = Promise.resolve({
      then: function(resolve, reject) {
        resolve('aaaa')
      }
    })
    // 相当于:
    /*
      const p = new Promise((resolve, reject) => {
        resolve({
          then: function(resolve, reject) {
            resolve('aaaa')
          }
        })
      })
    */
    console.log(p)
    

  • 参数为一个Promise;

    const p = Promise.resolve(new Promise((resolve, reject) => {
      resolve('abc')
    }))
    // 相当于:
    /*
      const p = new Promise((resolve, reject) => {
        resolve(new Promise((resolve, reject) => {
          resolve('abc')
        }))
      })
    */
    console.log(p)
    

6.2.reject方法

reject方法和resolve的用法一致,只不过是将可以得到一个状态为rejected的Promise对象,并且reject不过传入的是什么参数,都会原封不动作为rejected状态传递到catch中。

// 1.传入普通值
const p1 = Promise.reject(123)
p1.then(res => {
  console.log(res)
}).catch(err => {
  console.log('err:', err)
})

// 2.传入实现then方法对象
const p2 = Promise.reject({
  then: function(resolve, reject) {
    resolve('aaaa')
  }
})