JS Advance --- promise

292 阅读12分钟

出现的原因

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

Promise出现之前

function foo(url, successCallback, failCallback) {
  // 使用定时器模拟网络请求
  setTimeout(() => {
    if (url = 'example') {
      successCallback(213)
    } else {
      failCallback('err')
    }
  }, 2000)
}

foo('example',
msg => console.log(`success --- ${msg}`),
err => console.log(`fail --- ${err}`)
)

我们处理异步结果的方式是使用回调函数,但是异步函数怎么接收回调函数,以及回调函数的参数如何使用,都是有异步方法封装者自己定义的,这就意味着不同封装者的编码风格和封装的方式是不一样的

对于调用者而言,如果需要使用封装好的异步操作的方法的时候,就必须去查看封装者是如何进行封装的,也就是去查看文档,看API如何进行调用,这就大大增加了使用者和封装者之间的沟通成本

对此,在社区存在着一套社区规范,来规范异步操作的函数封装,即Promises/A+

而,在ES6中,对异步函数如何进行封装有了统一的官方方式,而这个就是Promise

promise最大的好处在于,promise使得异步方法可以像同步方法那样返回值,只不过返回的是一个承诺,是一个悬而未决的状态,这个状态在未来一定会转变为成功状态或失败状态

基本使用

  • Promise是一个类,可以翻译成 承诺、许诺 、期约
  • promise是一个承诺,也就是一个凭证,保证等会会告诉使用者,异步操作是否成功,并返回对应的结果
// Promsie接收一个参数,是一个回调函数,被称之为executor函数
// executor函数会在创建Promise实例的时候,被立即执行
new Promise(() => console.log('executor会被立即执行')) // => executor会被立即执行
function getResponse(url) {
  // 将需要执行的异步函数存放于executor函数中 --- 其中也是可以放同步代码的
  // executor需要传入两个参数,这两个参数的类型被要求为函数
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'example') {
        // 当成功的时候,执行resolve回调
        resolve('success')
      } else {
        // 当失败的时候,执行reject回调
        reject('fail')
      }
    }, 2000)
  })
}

const promise = getResponse('example')

// promise存在then方法
// 参数1 对应会在状态为resolved的时候被执行 executor的resolve参数 === 参数1
// 参数2 对应会在状态为rejected的时候被执行 executor的reject参数 === 参数2
promise.then(res => console.log(res), err => console.log(err))

三种状态

  • pending --- 未确定
    • 执行完executor函数时候的状态
    • 还并不知道异步请求最终会成功还是会失败
  • fulfilled (resolved) --- 异步成功
  • rejeced --- 异步失败

promise的状态一旦转变就会被锁定,就再也不能被转换

即 pending 可以转换为 resolved | rejected

但是reolved 和 rejected 两种状态之间是不可以相互转换的

function getResponse(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'example') {
        resolve('success')
        // 此时代码虽然不会报错
        // 但是reject并不会被执行  --- 静默失败
        reject('fail')
      } else {
        reject('fail')
      }
    }, 2000)
  })
}

const promise = getResponse('example')
promise.then(res => console.log(res), err => console.log(err))

resolve

参数是基本数据类型或普通对象 --- promise的状态为reolved

function getResponse(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'example') {
        resolve('success')
      } else {
        reject('fail')
      }
    }, 2000)
  })
}

const promise = getResponse('example')

promise.then(res => console.log(res), err => console.log(err)) // => success

参数类型是promise

promise的状态会被移交,也就是promise最终为什么状态由传入的promise决定

function getResponse(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'example') {
        // 虽然这里执行了resolve 状态为成功状态
        // 但是参数为一个新的promise,也就是状态决定权发生了移交
        // 也就是由参数Promise决定
        // 因此最后的结果是rejected
        resolve(new Promise((resolve, reject) => reject('失败了')))
      } else {
        reject('fail')
      }
    }, 2000)
  })
}

const promise = getResponse('example')
promise.then(res => console.log(res), err => console.log(err)) // => 失败了

参数是一个thenable的对象

当一个方法正确实现了then方法,就可以称这个对象为thenable的对象,其实就是一个实现then方法的对象

此时promise最终的状态由then方法决定

function getResponse(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'example') {
        resolve({
          // then方法会被自动执行
          then(resolve, reject) {
            // 最终状态由then方法决定
            reject("最终还是失败了")
          }
        })
      } else {
        reject('fail')
      }
    }, 2000)
  })
}

const promise = getResponse('example')
promise.then(res => console.log(res), err => console.log(err)) // => 失败了

实例方法

promise有三个实例方法: thencatchfinally

then

// then方法接收两个参数
// 参数1 对应会在状态为resolved的时候被执行 executor的resolve参数 === 参数1 --- 可选
// 参数2 对应会在状态为rejected的时候被执行 executor的reject参数 === 参数2 --- 可选

// then方法会将返回值转换为一个新的Promise,所以可以进行链式调用
promise.then(res => console.log(res), err => console.log(err))
       .then(res => console.log(res))

then方法可以被多次调用,对应的then方法会依次被执行

const promise = getResponse('example')

promise.then(() => console.log('success --- 1'))
promise.then(() => console.log('success --- 2'))
promise.then(() => console.log('success --- 3'))

/* 
  =>
    success --- 1
    success --- 2
    success --- 3
*/
then的返回值
  1. 返回值是普通值( 基本数据类型 + 普通对象 )
function getResponse(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'example') {
        resolve('success')
      } else {
        reject('fail')
      }
    }, 2000)
  })
}

const promise = getResponse('example')


promise.then(res => 123).then(res => console.log(res)) // => 123

/*
  会被转换为
  promise.then(res => new Promise((resolve, reject) => resolve(123)))
*/

一个函数没有任何返回值的时候,默认的返回值是undefined,所以如果一个then方法没有任何的返回值的时候,其就相当于返回值是一个新的promise实例,只不执行resolve方法的时候,为resolve(undefined)

  1. 返回值是一个Promise实例
function getResponse(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'example') {
        resolve('success')
      } else {
        reject('fail')
      }
    }, 2000)
  })
}

const promise = getResponse('example')


promise.then(res => new Promise((resolve, reject) => resolve('22222')))
       .then(res => console.log(res)) // => 22222

/*
  会被转换为
  promise.then(res => new Promise((resolve, reject) => {
    resolve(new Promise((resolve, reject) => resolve('22222')))
  })).then(res => console.log(res))
*/
  1. 返回值是一个thenable的对象
function getResponse(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'example') {
        resolve('success')
      } else {
        reject('fail')
      }
    }, 2000)
  })
}

const promise = getResponse('example')


promise.then(res => ({
  then(resolved, rejected) {
    resolved(111111)
  }
})).then(res => console.log(res)) // => 111111

/*
  会被转换为
  promise.then(res => new Promise((resolve, reject) => {
    resolve({
      then(resolved, rejected) {
        resolved(111111)
      }
    })
  })).then(res => console.log(res))
*/

catch

当我们只需要捕获reject状态的时候,我们一般会将then方法的第一个参数设置为undefined

promise.then(undefined, () => console.log('error'))

对此ES6,提供了对应的语法糖形式的代码

promise.catch(() => console.log('error'))

当一个promise执行了reject函数或者抛出一个异常的时候,promise都会进入rejected状态,所以catch方法都会被触发

function getResponse(url) {
  return new Promise((resolve, reject) => {
    // 当promise抛出异常的时候 --- 状态会转换为rejected
    // 并将对应的错误对象作为reject方法的参数进行传递
    throw new Error('error')
  })
}

const promise = getResponse()

promise.catch(err => {
  console.log('err', err)
})

如果存在多个catch方法,多个catch方法会被依次执行

function getResponse(url) {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}

const promise = getResponse()

promise.catch(() => console.log('err1'))
promise.catch(() => console.log('err2'))
promise.catch(() => console.log('err3'))

promise的返回值依旧是一个promise

promise.then(() => console.log('success'),() => console.log('error'))

可以被转变为

promise.then(() => console.log('success'))
       .catch(() => console.log('error'))

注意:

function getResponse(url) {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}

const promise = getResponse()

// 下面两行其实是promise的两次调用
// 所以对于第11行而言,其只实现了resolve方法,并没有执行reject方法
// 所以当promise状态为rejected的时候,就会报错
promise.then(() => console.log('success'))
promise.catch(() => console.log('error'))

如果一个catch对应多个promise,那么这个catch会捕获到第一个状态为reject的promise,并终止所有promise的执行

function getResponse(url) {
  return new Promise((resolve, reject) => {
    resolve('success')
  })
}

const promise = getResponse()

// promise.then方法的返回的promise为reject的时候,catch方法会被触发
promise
  .then(() => new Promise((resolve, reject) => reject('出错了')))
  .catch(() => console.log('reject')) // => reject 
function getResponse(url) {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}

const promise = getResponse()

// getResponse方法的返回的promise为reject的时候,catch方法会被触发
promise
  .then(() => new Promise((resolve, reject) => reject('出错了')))
  .catch(() => console.log('reject')) // => reject

catch方法的返回值

catch方法的返回值也是一个promise实例,所以其返回值的规则和resolve方法的返回值所对应的规则是一致的

function getResponse(url) {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}

const promise = getResponse()

promise
  .catch(() => {
    console.log('reject')
    return 'catch方法的返回值'
  })
  // 因为catch方法的返回值也会被转换为一个新的promise实例
  // 所以在该案例中,catch方法后边的then方法也会被正常执行
  .then(res => console.log(res)) // => catch方法的返回值

finally

finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会 被执行的代码

finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行

function getResponse(url) {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}

const promise = getResponse()

promise
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('一定会被执行的代码'))

类方法

resolve

Promise.resolve的用法相当于new Promise,并且执行resolve操作

const promise = Promise.resolve({ name: 'Klaus' })
promise.then(res => console.log(res))

等价于

const promise = new Promise((resolve, reject) => resolve({ name: 'Klaus' }))
promise.then(res => console.log(res))

Promise.resolve方法可以接收的参数类型为

  • 参数是一个普通的值或者对象
  • 参数本身是Promise
  • 参数是一个thenable

具体判断规则和之前是一样的

reject

Promise.reject的用法相当于new Promise,只是会调用reject方法

Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的

const promise = Promise.reject('reject')
promise.catch(res => console.log(res))

等价于

const promise = new Promise((resolve, reject) => reject('error'))
promise.catch(res => console.log(res))

all

作用是将多个Promise包裹在一起形成一个新的Promise

新的Promise状态由包裹的所有Promise共同决定

  • 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值 组成一个数组
  • 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数
const p1 = new Promise((resolve, reject) => setTimeout(() => resolve('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => resolve('22222'), 2000))
const p3 = new Promise((resolve, reject) => setTimeout(() => resolve('33333'), 3000))

// Promise.all方法的返回值依旧是一个promise实例
Promise.all([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err))
// => [ '11111', '22222', '33333' ]
const p1 = new Promise((resolve, reject) => setTimeout(() => resolve('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => resolve('22222'), 2000))
const p3 = new Promise((resolve, reject) => setTimeout(() => resolve('33333'), 3000))

// 输出的结果顺序是由参数的顺序决定的,而不是由那个promise先转变为fulfilled状态而定的
// 即使p1比p2先确定状态为fulfilled,但是p1的结果依旧是作为数组的第二项值被输出
Promise.all([p2, p1, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err))
// => [ '22222', '11111', '33333' ]
const p1 = new Promise((resolve, reject) => setTimeout(() => resolve('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('22222'), 2000))
const p3 = new Promise((resolve, reject) => setTimeout(() => resolve('33333'), 3000))

// 当一个promise的状态被确定为rejected, 直接进入catch
Promise.all([p2, p1, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err))
// => 22222

allsettled

方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态

allsettled方法的返回值也是一个promise, 并且这个Promise的结果一定是fulfilled的

const p1 = new Promise((resolve, reject) => setTimeout(() => resolve('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('22222'), 2000))
const p3 = new Promise((resolve, reject) => setTimeout(() => resolve('33333'), 3000))

Promise.allSettled([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err))
/* 
  => 
    [
      { status: 'fulfilled', value: '11111' },
      { status: 'rejected', reason: '22222' },
      { status: 'fulfilled', value: '33333' }
    ]
*/

race

race方法的参数接收多个promise组成的数组,返回值是一个promise

返回的promise的状态由第一个状态确定的promise决定

如果第一个有状态的promise的状态为resolved,那么返回的promise的状态就是resolved

如果第一个有状态的promise的状态为rejected,那么返回的promise的状态就是rejected

const p1 = new Promise((resolve, reject) => setTimeout(() => resolve('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('22222'), 2000))
const p3 = new Promise((resolve, reject) => setTimeout(() => resolve('33333'), 3000))

Promise.race([p1, p2, p3])
  .then(res => console.log('res', res))
  .catch(err => console.log('err', err))
/*
  => res 11111
*/
const p1 = new Promise((resolve, reject) => setTimeout(() => resolve('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('22222'), 500))
const p3 = new Promise((resolve, reject) => setTimeout(() => resolve('33333'), 3000))

Promise.race([p1, p2, p3])
  .then(res => console.log('res', res))
  .catch(err => console.log('err', err))
/*
  => err 22222
*/

any

any方法是ES12中新增的方法,和race方法是类似的

any方法会等到一个fulfilled状态,才会决定新Promise的状态

如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态

如果所有的Promise都是reject的,那么会报一个AggregateError的错误

const p1 = new Promise((resolve, reject) => setTimeout(() => resolve('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('22222'), 500))
const p3 = new Promise((resolve, reject) => setTimeout(() => resolve('33333'), 3000))

Promise.any([p1, p2, p3])
  .then(res => console.log('res', res))
  .catch(err => console.log('err', err))
/*
  => res: 11111
*/
const p1 = new Promise((resolve, reject) => setTimeout(() => reject('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('22222'), 500))
const p3 = new Promise((resolve, reject) => setTimeout(() => reject('33333'), 3000))

Promise.any([p1, p2, p3])
  .then(res => console.log('res', res))
  .catch(err => console.log('err', err))
/*
  => err:  AggregateError: All promises were rejected
  返回的是一个AggregateError实例
  相当于Promise.any内部reject方法执行的时候为
  reject(new AggregateError('All promises were rejected'))
*/
const p1 = new Promise((resolve, reject) => setTimeout(() => reject('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('22222'), 500))
const p3 = new Promise((resolve, reject) => setTimeout(() => reject('33333'), 3000))

// 对于AggregateError实例,我们可以使用errors方法获取到所有 参数promise 在执行reject方法时候,传入的参数
// 类型为数组
Promise.any([p1, p2, p3])
  .then(res => console.log('res', res))
  .catch(err => console.log(err.errors))
// =>  ['11111', '22222', '33333']
const p1 = new Promise((resolve, reject) => setTimeout(() => reject('11111'), 1000))
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('22222'), 500))
const p3 = new Promise((resolve, reject) => setTimeout(() => reject('33333'), 3000))

// 当然,这里的错误信息的输出顺序依旧是和传入的参数的顺序是一一对应的
// 而不是和promise状态的转变顺序对应
Promise.any([p2, p1, p3])
  .then(res => console.log('res', res))
  .catch(err => console.log(err.errors))
// =>  ['22222', '11111', '33333']