Promise 基本使用 & 手写 Promise

173 阅读8分钟

一、Promise 简介

1. 对 Promise 的理解

Promise 是 JS 中进行异步编程的新的解决方案。

  • 从语法上看,Promise 是一个构造函数,使用 new 关键字,可以得到一个 promise 实例对象
  • 从功能上看,promise 对象可以用来封装异步操作,通过 then() 可以获取其成功或失败的结果

2. promise 的状态与值

const p = new Promise(() => {})
console.log(p)

image.png

2.1 promise 的状态(PromiseState)

promise 有三个状态:pendingresolvedrejected

  • 一个 Promise 刚被 new 出来的时候,状态是 pending(未确定的)
  • 当执行了 resolve(),状态由 pending 变为 resolved(成功)
  • 当执行了 reject(),状态由 pending 变为 rejected(失败)

注意:promise 的状态只能改变一次,重复修改不起作用。也就是说,一旦状态改变了,状态就固定了(resolvedrejected),不会被再次改变。

2.2 promise 的值(PromiseResult)

无论 promise 的状态是成功还是失败,都会有一个结果数据。成功的结果数据一般称为 value,失败的结果数据一般称为 reason

3. Promise 的基本流程

image.png

二、Promise 使用

1. Promise 构造函数的基本使用

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

  • resolve 函数的作用是,将 promise 对象的状态从 pending 变为 resolved,在异步操作成功时调用,并将异步操作的结果,作为参数 value 传递出去;
  • reject 函数的作用是,将 promise 对象的状态从 pending 变为 rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数 error / reason 传递出去。
const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        const time = Date.now()
        if (time % 2 === 0) {    // 当前时间是偶数代表成功,否则代表失败
            resolve(1)
        } else {
            reject(2)
        }
    }, 2000)
})

2. Promise.prototype.then()

Promise 实例对象生成以后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数。

then 方法接受两个回调函数作为参数(两个参数都是可选的):

  • 第一个回调函数 onResolved(value),是 promise 对象的状态变为 resolved 时调用
  • 第二个回调函数 onRejected(reason),是 promise 对象的状态变为 rejected 时调用

then 方法的返回值是一个新的 promise 对象,意味着 .then() 之后可以继续 .then()

const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        const time = Date.now()
        if (time % 2 === 0) {    // 当前时间是偶数代表成功,否则代表失败
            resolve(1)
        } else {
            reject(2)
        }
    }, 2000)
})

p.then(value => {               // onResolved 函数,value 是 resolve 传递出来的值
    console.log(value)
}, reason => {                  // onRejected 函数,reason 是 reject 传递出来的值
    console.log(reason)
}).then(value => {              // 此时的 value 是上一个 then 返回的 promise 的成功的值
    console.log(value)
}, reason => {                  // 此时的 reason 是上一个 then 返回的 promise 的失败的值
    console.log(reason)
})

3. Promise.prototype.catch()

catch 方法用于指定失败的回调函数,相当于 then 的第二个回调函数 onRejected

// p 是一个 promise 对象
p.catch(error => {
    console.log(error)
})

// 相当于
p.then(null, reason => {
    console.log(reason)
})

一般放到 then 之后用于捕获错误。

// p 是一个 promise 对象
p.then(value => {
    console.log(value)
    throw new Error('出错了')
}, reason => {
    console.log(reason)
}).catch(error => {       // 当 then 中的逻辑抛出错误,就会被 catch 捕获到,error 就是抛出的错误信息
    console.log(error)
})

4. Promise.resolve()

参数:非 Promise 类型的值或 promise 对象。

返回值:

  • 如果参数是非 Promise 类型的值,就返回一个成功的 promise 对象
  • 如果参数是 promise 对象,就根据这个 promise 的结果,返回一个成功的或失败的 promise 对象
// 参数是非 Promise 类型的值
const p = Promise.resolve(123)
console.log(p)

image.png

// 参数是成功的 promise 对象
const promise = new Promise((resolve, reject) => {
  resolve(123)
})
const p = Promise.resolve(promise)
console.log(p)

image.png

// 参数是失败的 promise 对象
const promise = new Promise((resolve, reject) => {
  reject(123)
})
const p = Promise.resolve(promise)
console.log(p)

image.png

5. Promise.reject()

参数:非 Promise 类型的值或 promise 对象。

返回值:返回一个失败的 promise。

// 参数是非 Promise 类型的值
const p = Promise.reject(123)
console.log(p)

image.png

// 参数是成功的 promise 对象
const promise = new Promise((resolve, reject) => {
  resolve(123)
})
const p = Promise.reject(promise)
console.log(p)

image.png

// 参数是失败的 promise 对象
const promise = new Promise((resolve, reject) => {
  reject(123)
})
const p = Promise.reject(promise)
console.log(p)

image.png

6. Promise.all()

参数:包含多个 promise 对象的数组。

返回值:

  • 当所有 promise 都成功,就返回一个成功的 promise,promise 的值是一个数组,数组中的元素就是参数数组中所有 promise resolve 出来的值,且数组中值的顺序与参数数组中 promise 的顺序保持一致
  • 当有一个 promise 失败,就返回一个失败的 promise,promise 的值就是失败的那个 promise 的值
  • 当有多个 promise 失败,就返回一个失败的 promise,promise 的值就是最先失败的那个 promise 的值
// 所有 promise 都成功
const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const p3 = Promise.resolve(3)
const pAll = Promise.all([p1, p2, p3])
console.log('pAll:', pAll)

image.png

// 有一个 promise 失败
const p1 = Promise.resolve(1)
const p2 = Promise.reject(2)
const p3 = Promise.resolve(3)
const pAll = Promise.all([p1, p2, p3])
console.log('pAll:', pAll)

image.png

// 有多个 promise 失败
const p1 = Promise.resolve(1)
const p2 = Promise.reject(2)
const p3 = Promise.reject(3)
const pAll = Promise.all([p1, p3, p2])
console.log('pAll:', pAll)

image.png

7. Promise.race()

参数:包含多个 promise 对象的数组。

返回值:返回一个 promise,promise 的值就是参数数组中最先返回结果(无论成功或失败)的 promise 的值。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
const p2 = Promise.reject(2)
const p3 = Promise.reject(3)
const pRace = Promise.race([p1, p3, p2])
console.log('pRace:', pRace)

image.png

三、Promise 的几个关键问题

1. 如何改变 promise 的状态

当执行 new Promise(() => {}) 时,就会返回一个 promise 对象,此时 promise 的状态是 pending

  • pending 变为 resolved:调用 resolve()
  • pending 变为 rejected
    1. 调用 reject()
    2. 抛出异常:throw new Error
new Promise((resolve, reject) => {
  resolve(1)               // 状态从 pending 变成 resolved
})
.then(value => {           // 调用 then 中的 onResolved 回调
  console.log('value:', value)
})

// value: 1
new Promise((resolve, reject) => {
  reject(1)                // 状态从 pending 变成 rejected
})
.then(value => {
  console.log('value:', value)
}, reason => {             // 调用 then 中的 onRejected 回调
  console.log('reason:', reason)
})

// reason: 1
new Promise((resolve, reject) => {
  throw 1                 // 状态从 pending 变成 rejected
})
.then(value => {
  console.log(value)
}, reason => {            // 调用 then 中的 onRejected 回调
  console.log('reason:', reason)
})

// reason: 1

2. 一个 promise 指定多个成功/失败回调函数,都会调用吗

当 promise 改变为对应状态时,多个 then 中对应的回调都会调用。

注意,这里不是指多个 then 链式调用。

const p = new Promise((resolve, reject) => {
  resolve(1)
})

p.then(value => {
  console.log('value1:', value)
}, reason => {
  console.log('reason1:', reason)
})

p.then(value => {
  console.log('value2:', value)
}, reason => {
  console.log('reason2:', reason)
})

// value1: 1
// value2: 1

3. 改变 promise 状态和指定回调函数谁先谁后

可以先改变状态,后指定回调;也可以先指定回调,后改变状态。

无论是先改变状态后指定回调,还是先指定回调后改变状态,当状态发生改变时,then 中的回调函数都能拿到对应状态的数据。

3.1 先改变状态,后指定回调

const p = new Promise((resolve, reject) => {    // 这里的回调是同步回调,会立即执行回调函数中的代码
  resolve(1)
})

p.then(value => {          // then 中的回调是异步回调,不会立即执行,它会等 promise 状态改变了再执行
  console.log('value1:', value)
}, reason => {
  console.log('reason1:', reason)
})

// value1: 1

3.2 先指定回调,后改变状态

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)  
  }, 1000)
})

p.then(value => {
  console.log('value1:', value)
}, reason => {
  console.log('reason1:', reason)
})

// value1: 1

4. promise.then() 返回的新 promise 的结果状态由什么决定

返回的新 promise 的状态由 then() 中的回调函数执行的结果决定:

  • 如果回调抛出异常,就返回失败的 promise;
  • 如果返回的是非 promise 的值,就返回成功的 promise;
  • 如果返回的是 promise 对象,此 promise 就会作为新 promise 返回。
// 如果回调抛出异常,就返回失败的 promise
new Promise((resolve, reject) => {
  resolve(1)
})
.then(value => {
  console.log('value1:', value)
  throw value                       // 抛出异常,返回失败的 promise
}, reason => {
  console.log('reason1:', reason)
})
.then(value => {
  console.log('value2:', value)
  throw value
}, reason => {                  // 由于上一个 then 返回失败的 promise,所以这里执行 onRejected 回调
  console.log('reason2:', reason)
})

// value1: 1
// reason2: 1
// 如果返回的是非 promise 的值,就返回成功的 promise
new Promise((resolve, reject) => {
  reject(1)
})
.then(value => {
  console.log('value1:', value)
}, reason => {
  console.log('reason1:', reason)
  return reason+1                   // 正常返回值
})
.then(value => {                    // 由于上一个 then 没有抛出异常,所以这里执行 onResolved 回调
  console.log('value2:', value)     // 这里的 value 就是上一个 then 执行的回调
}, reason => {
  console.log('reason2:', reason)
})

// reason1: 1
// value2: 2
// 如果返回的是成功的 promise 对象
new Promise((resolve, reject) => {
  reject(1)
})
.then(value => {
  console.log('value1:', value)
}, reason => {
  console.log('reason1:', reason)
  return Promise.resolve(123)   // 返回成功的 promise
})
.then(value => {                // 由于上一个 then 返回成功的 promise,所以这里执行 onResolved 回调
  console.log('value2:', value)
}, reason => {
  console.log('reason2:', reason)
})

// reason1: 1
// value2: 123
// 如果返回的是失败的 promise 对象
new Promise((resolve, reject) => {
  reject(1)
})
.then(value => {
  console.log('value1:', value)
}, reason => {
  console.log('reason1:', reason)
  return Promise.reject(123)    // 返回失败的 promise
})
.then(value => {                
  console.log('value2:', value)
}, reason => {                  // 由于上一个 then 返回失败的 promise,所以这里执行 onRejected 回调
  console.log('reason2:', reason)
})

// reason1: 1
// reason2: 123

5. promise 如何串联多个操作任务

写法同上(第 4 点),不再赘述。

6. promise 异常传透

当使用 promise 的 then 链式调用时,可以在最后使用 catch 指定失败的回调。前面任何操作出了异常,都会传到最后的 catch 中。

new Promise((resolve, reject) => {
  reject(1)
})
.then(value => {
  console.log('value1:', value)
  return 2
})
.then(value => {
  console.log('value2:', value)
  return 3
})
.then(value => {
  console.log('value3:', value)
})
.catch(error => {
  console.log('error:', error)
})

// error: 1

7. 中断 promise 链

当使用 promise 的 then 链式调用时,可以在中间的某个回调函数中返回一个 pending 状态的 promise 对象,调用链就会在这里中断,不再调用后面的回调函数。

new Promise((resolve, reject) => {
  reject(1)
})
.then(value => {
  console.log('value1:', value)
  return 2
})
.then(value => {
  console.log('value2:', value)
  return 3
})
.then(value => {
  console.log('value3:', value)
})
.catch(error => {
  console.log('error:', error)
  return new Promise(() => {})    // 返回状态为 pending 的 promise
})
.then(value => {  // 由于上一个 catch 的回调返回 pending 状态的 promise,所以 then 中的回调不会执行
  console.log('value:', value)
})

四、手写 promise

实现一个 MyPromise 构造函数