Promise 入门,不妨看看这篇文章

2,199 阅读9分钟

前言

我们都知道,Javascript其中最大的特点便是它的语言执行环境是单线程,即一次只能完成一个任务。这就意味着,所有任务都需要排队,执行完一个任务再执行下一个任务。看似简单的执行模式,却有可能因为其中某个任务执行时间过长导致后续的任务被拖延执行,从而影响整个程序的执行

同步与异步

由于Javascript是单线程语言这一特点,为了能够更好的解决单线程带来的问题,Javascript将任务的执行模式分为两种:同步(Synchronous)异步(Asynchronous)

同步模式编程

同步模式便是前言所说的,所有任务进行排列依次逐个执行,执行顺序与任务排列顺序是一致的

console.log('任务1')

for (let i = 0; i < 10; i++) {
  console.log('任务2')
}

console.log('任务3')

;(function(){
  console.log('任务4')
}())

异步模式编程

完成异步编程,最基本的方式便是定义纯回调函数(callback),任务中有一个或多个回调函数

常见的异步代码: 定时器、 事件绑定、 Ajax ...

;(function() {

  console.log('任务1,立即执行')
  
  setTimeout(function() {
    console.log('任务1-2,5秒后执行')
  }, 5000)
  
}())

console.log('任务2', '稍后执行')

定时器作为一个异步操作,后续的任务不会因为异步操作导致堵塞执行,与同步模式相比,异步模式中执行顺序与任务排列顺序是不一致的

异步编程带来的问题

级级联动的 http 请求、复杂的业务场景、或是为了优化计算量大而时间长的操作,串联多个异步操作也并非不可能,你是否见过使用多层嵌套的回调函数解决问题

getDepartment('', ({success, data}) => {
  if (success) {
    // ....
    getStaffsByDepartmentId(data[0].id, ({success, data}) => {
      if (success) {
        //...
        getTrainDetail(data[0].id, ({success, data}) => {
          if (success) {
            // ...
          }
        })
      }
    })
  }
})

随着代码向右不断延伸、不断嵌套,不具备扩展性的同时,嵌套形式的代码夹杂着复杂的业务逻辑,维护起来极其不便,回调地狱因此得名

应运而生的 Promise

很幸运,本人并没有经历过回调地狱的噩梦。与传统多层嵌套式的回调函数相比,不可预测的执行时间与执行结果,使用Promise则更加强大,毕竟谁都不想越陷越深

Promise 是什么

参考 ECMAScript 6 入门 可知,Promise 是异步编程的一种解决方案。它由社区最早提出和实现,ES6 将其写进了语言标准

简单来说,Promise 用来封装一个异步操作并可以获取其结果和状态的对象,是异步编程的一种解决方案

Promise 基本使用

生成promise实例

  1. Promise是一个构造函数,通过 new 操作符可创建一个 promise 实例

  2. 该构造函数接收一个函数作为参数,这个函数我们通常称为执行器(executor)函数

  3. 执行器函数接收两个参数:这两个参数作为函数可调用并可以传入相应数据,通常我们以resolvereject进行命名

    • 第一个参数 resolve :异步操作执行成功后的回调函数
    • 第二个参数 reject:异步操作执行失败后的回调函数
const promise = new Promise((resolve, reject) => { // 执行器
  // 异步操作
  setTimeout(() => {
    const rand = Math.random() * 100
    // 模拟成功或失败
    if (rand >= 50) {
        // 成功时执行
      resolve()
    } else {
        // 失败时执行
      reject()
    }
  }, 1000)
})

Promise 的状态

promise实例具有三种状态

  • pending(进行中,最初状态)
  • fulfilled:(已成功,有时候也称为resolved)
  • rejected:(已失败)

状态的改变只有两种可能:

  • pending 变为 fulfilled
  • pending 变为 rejected

promise 实例的状态是私有的,我们只能通过执行器(executor)函数来完成状态的转换,控制状态的改变则是通过调用执行器函数内的 resolve、reject函数实现。执行器内抛出异常,也是可以将pending 状态变为 rejected。 不管状态最终从 pending 变为 fulfilled 还是 rejected,只要 promise 实例的状态发生了改变,该状态就已经是落定了,不可逆的,将来都不再会发生改变

new Promise((resolve, reject) => {}) // pending 最初状态

new Promise((resolve, reject) => resolve()) // fulfilled 已成功状态

new Promise((resolve, reject) => reject()) // rejected 已失败状态

new Promise((resolve, reject) => { throw new Error('error') }) // rejected 已失败状态

resolve函数的作用:

promise实例的状态从 pending 变为 fulfilled,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去

reject函数的作用:

将 promise 实例的状态从 pending 变为 rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

then 方法

Promise原型对象上具有then方法,该方法是为 promise 实例指定处理程序的主要方法,promise 实例可以直接调用,then 方法接收两个可选参数(onResolved 处理程序,onRejected 处理程序),该方法返回一个新的 promise 实例

  • 提供第一个参数onResolved处理程序:fulfilled状态的回调函数
  • 提供第二个参数onRejected处理程序:rejected状态的回调函数
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const rand = Math.random() * 100
    if (rand >= 50) {
        // 执行resolve 并传入相应数据
      resolve({name: 'James', age: 36})
    } else {
       // 执行reject 并传入异常
      reject(new Error('Error'))
    }
  }, 1000)
})

promise.then(
  (value) => {
    // 当promise实例的状态为 fulfilled 时执行
    console.log('执行成功时的结果值数据', value)
  },
  (error) => {
    // 当promise实例的状态为 rejected 时执行
    console.log('执行失败时的结果值数据', error)
  }
)

因为 promise 实例的状态发生改变,只会存在一种结果,传入两个处理程序,要么状态变为 fulfilled,onResolved 执行,要么状态变为 rejected,onRejected 执行。当然,若状态永远处于pending,则这两个处理程序都不会执行

关于可选参数

then方法的两个参数都是可选的,参考《Javascript高级程序设计》,提供任何非函数类型的参数都会被静默忽略,例如:如果想只提供 onRejected 参数,那就要在 onResolved 参数的位置上传入 null 或 undefined。这样有助于避免在内存中创建多余的对象

  // 只指定 promise 状态为 rejected 的处理程序
promise.then(null, 
  (error) => {
    console.log('执行失败时的结果值数据', error)
  }
)
  //  只指定 promise 状态为 fulfilled 的处理程序
promise.then((value) => {
  console.log('执行成功时的结果值数据', value)
})

关于链式调用

前面我们提到,then方法最终会返回一个新的 promise 实例,因此可以进行链式调用,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({name: 'James', age: 36})
  }, 1000)
})

promise.then(
  (value) => {
    console.log('onResolved1', value)
    return value
  },
).then(
  (value) => {
    console.log('onResolved2', value)
    return value
  },
).then(
  (value) => {
    console.log('onResolved3', value)
    return value
  }
)

image.png

关于状态值与结果值

  • 处理程序中抛出异常,新 promise 实例的状态值为 rejected,结果值便是抛出的异常
  • 处理程序中返回非 promise 的任意值,新 promise 实例的状态值为 fulfilled,结果值便是返回值
  • 处理程序中手动返回一个新 promise 例如:promise.resolve、promise.reject,此 promise 的结果便是新 promise 的结果

catch 方法

Promise原型对象上具有catch方法,该方法是为 promise 实例指定失败(拒绝)处理程序,相当于.then(null, (error) => {})语法糖

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error('error'))
  }, 1000)
})

promise.then((value) => {
  console.log(value)
}).catch((error) => {
  console.log(error)
})

使用 catch 方法代替 then 方法的第二个参数

前面说到 then 方法第二个可选参数(onRejected 处理程序),该处理程序是当 promise 实例 状态为rejected 时调用。有了catch方法,我们便可以省略 then 方法的第二个参数,通过 catch 方法处理异常

// bad
Promise.then(
  (value) => { // ... },
  (error) => { // ... }
).then(
  (value) => { // ... },
  (error) => { // ... }
)

// good
Promise
  .then((value) => { // ... })
  .then((value) => { // ... })
  .catch((error) => { // ... })

它们之中任何一个抛出的错误,都会被最后一个catch()捕获,

关于 Promise 的其他 Api

在 Promise 基本使用中,我们介绍到了 Promise 原型对象上 then、catch 方法,下面再介绍一下 关于 Promise 其他 Api

Promise.resolve

resolve 是 Promise 的一个静态方法。此前,我们知道通过 new 调用 Promise 构造函数可以生成一个 promise 实例,promise 实例的初始状态是 pending,并且想要改变 promise 实例的状态,只能在执行器内完成。调用 Promise.resolve() 将返回一个状态为 fulfilled 的 promise 实例

Promise.resolve()

// 等价于

new Promise((resolve) => resolve())

非 promise 实例作为参数

Promise.resolve({name: 'James'}) // Promise {<fulfilled>: {name: 'James'}

Promise.resolve('Hello Promise') // Promise {<fulfilled>: 'Hello Promise'}

Promise.resolve() // Promise {<fulfilled>: undefined}

Promise.resolve(new Error('error')) // Promise {<fulfilled>: Error: error

Promise.resolve 方法传入任何非 promise 实例,都将返回一个状态为 fulfilled 的 promise 实例

promise 实例作为参数

const p1 = new Promise((resolve, reject) => resolve({name: 'James', age: 36}))
const p2 = Promise.resolve({name: 'Curry', age: 33})

Promise.resolve(p1) // p1 实例
Promise.resolve(p2) // p2 实例

参数是 promise 实例,那么 Promise.resolve 将不做任何修改、原封不动地返回这个实例

Promise.reject

Promise.reject 与 Promise.resolve 类似,作为一个静态方法。调用Promise.reject(),将返回一个状态为 reject 的 promise 实例

Promise.reject()

// 等价于

new Promise((resolve, reject) => reject())

关于 Promise.reject 的错误捕获

通过 try/catch 是无法捕获 Promise.reject 的错误的

try {
  Promise.reject('Error')
} catch (error) {
  console.log('catch', error)
}
// Uncaught (in promise) Error

通过 Promise.catch 方法处理异常

Promise.reject('Error').catch((error) => {
  console.log('catch', error) // catch Error
})

Promise.all

Promise.all 该静态方法,用于处理多个 promise 实例,接收一个可迭代对象,最终返回一个新的 promise 实例

const p1 = new Promise((resolve, reject) => resolve({name: 'James', age: 36}))
const p2 = Promise.resolve({name: 'Curry', age: 33})

Promise.all([p1, p2])
  .then((value) => {
    console.log(value) // [{name: 'James', age: 36}, {name: 'Curry', age: 33}]
  })
  .catch((error) => {
    console.log(error)
  })

上面我们传入了两个状态都为 fulfilled 的 promise 实例,返回的新 promise 实例的状态为 fulfilled,其结果值,便是按照迭代器顺序返回的数组

状态值的影响

  • 包含一个状态值为 pending 的 promise 实例
const p1 = new Promise((resolve, reject) => {})
const p2 = new Promise((resolve, reject) => resolve({name: 'James', age: 36}))
const p3 = Promise.resolve({name: 'Curry', age: 33})

const promise = Promise.all([p1, p2, p3])

setTimeout(() => {
  console.log(promise) // Promise {<pending>}
}, 1000)
  • 所有 promise 实例的状态值都为 fulfilled
const p1 = new Promise((resolve, reject) => resolve({name: 'James', age: 36}))
const p2 = Promise.resolve({name: 'Curry', age: 33})

const promise = Promise.all([p1, p2])

setTimeout(() => {
  console.log(promise) // Promise {<fulfilled>: Array(2)}
}, 1000)
  • 包含一个状态值为 rejected 的 promise 实例
const p1 = new Promise((resolve, reject) => reject())
const p2 = new Promise((resolve, reject) => resolve({name: 'James', age: 36}))
const p3 = Promise.resolve({name: 'Curry', age: 33})

const promise = Promise.all([p1, p2, p3])

setTimeout(() => {
  console.log(promise) // Promise {<rejected>: undefined}
}, 1000)

传入的所有 promise 实例的状态值为 fulfilled,新返回的 promise 实例的状态值才为 fulfilled,若传入的 promise 实例状态其中包含 pending 或 rejected,那么新返回的 promise 实例的状态值便是 pending 或 rejected

Promise.race

Promise.race 该静态方法接收一个可迭代对象,最终返回一个新的 promise 实例,与Promise.all 不同之处便是,不管传入何种状态的 promise 实例,Promise.race 最终只会需要第一个状态发生改变的 promise 实例,并返回一个新的 promise 实例

const p1 = new Promise((resolve) => {})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({name: 'James', age: 36})
  }, 1000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('error')
  }, 2000)
})

const promise = Promise.race([p1, p2, p3])
  .then((value) => {
    console.log('onResolved', value) // onResolved {name: 'James', age: 36}
  })
  .catch((error) => {
    console.log('onRejected', error)
  })

写在最后

以上便是我对 Promise 在应用上的一些简单理解,希望各位看客多多支持,若有哪些地方描述的不清晰,也请多多见谅并指点一二😁