JavaScript中的Promise

阮一峰 ES6入门 Promise

1. Promise 的介绍

Promise 是异步编程的一种新的解决方案,从早期的回调函数事件相比,更加合理和强大。语法上来说,Promise 是一个对象,可以获取异步操作成功或是失败的结果。

Promise 有三种状态:pending (进行中),fulfilled (resolved 已成功),rejecred (已失败)。只有当异步操作的结果可以决定当前是哪一种状态,任意其他操作都无法改变这个状态。

一旦状态发生改变,就不会再变,任意时刻都可以获取到这个结果。Promise 对象的状态改变只有两种情况:从pending (进行中)改变为resolved (已成功),从pending (进行中)改变为rejected (已失败)

1.1 Promise 的优缺点

而通过 Promise 对象就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

当然,Promise 也有一些缺点,例如:

  • 无法取消 Promise,一旦新建就会立即执行,无法中途取消
  • 如果不设置回调函数。Promise 抛出的错误不会反应到外界
  • 当处于 pending 状态时,无法得知目前进展到哪一个阶段 ( 是刚刚开始执行还是即将完成操作 )

2. Promise 的基本用法

2.1 生成实例

ES6规定,Promise 对象是一个构造函数,用来生成 Promise 实例。

// 这就简单的创建了一个 Promise 实例
new Promise((resolve, reject) => {
  if(/* 异步操作是否成功 */) {
    resolve(value)
  }else {
    reject(error)
  }
})

上面的代码中,可以看到 Promise 构造函数接受了一个函数作为参数,这个函数有携带两个参数: resolvereject。这两个参数都是函数,由 JavaScript 引擎提供,不需要自己传参。

  1. resolve:将 Promise 对象的状态改变为:resolved (已成功),可以在异步操作完成后进行执行,并且可以将异步操作的结果作为参数传递出去。
  2. reject:将 Promise 对象的状态改变为:rejected (已失败),同样在异步操作完成后进行执行,同样可以将异步操作的结果作为参数传递出去。

2.2 使用 then() 指定回调函数

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

then() 方法接受两个函数作为参数,其中,第一个作为 resolved 的回调函数,第二个作为 rejected 的回调函数,两个函数都是可选的,不一定非要提供。

new Promise((resolve, reject) => {
  // reject('拒绝状态')
  resolve('成功状态')
})
  .then(
    (value) => {
      console.log('成功的业务处理')
    },
    (reason) => {
      console.log('拒绝的业务处理')
    }
  )
// 简单的伪装为一个网络请求
let statusCode = 404  // 假设这是请求返回的状态码

// 实例化 Promise 对象
const p = new Promise(function (resolve, reject) {
   // 成功
   if ( statusCode >= 200 && statusCode < 300 ) {
     let data = '数据库中的用户数据'
     resolve(data)
   }
   // 失败
   else if (statusCode === 404) {
     let err = '获取数据失败'
     reject(err)
   }
})

// 调用 promise 对象的 then 方法
p.then(
  function (value) {
    console.log(value)
  },
  function (reason) {
    console.log(reason)
  }
)

2.3 链式调用 then()

Promise.then() 其实也一个 Promise,同样会返回状态。

只是没有设置的情况的默认返回的就是 resolved 状态,且不会携带任何值。

哪怕手动返回了一个 非Promise类型 的值,也会被自动转换为 Promise.resolve() 的参数。

// 创建一个 Promise 实例,修改状态为 reject
let p1 = new Promise((resolve, reject) => {
  reject('拒绝')
})

let p2 = p1
  .then(
    (value) => {
      console.log(value)
    },
    (error) => {
      console.log(error)
      // 如果这里不手动返回一个 Promise 的 reject 状态,下一个 then 就会执行 resolve 状态的回调函数
      // return new Promise((resolve, reject)=>{
      //   reject('失败')
      // })
    }
  )
  .then(
    (value) => {
      console.log('成功')
    },
    (error) => {
      console.log(error)
    }
  )

// 输出结果为:
// 拒绝
// 成功

3. Promise.resolve() 和 Promise.reject()

Promise.resolve()Promise.reject() 可以将一个现有对象转换为 Promise 对象。

Promise.resolve()
Promise.reject()

// 就相当于

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

4. Promise.prototype.catch()

在上面的例子中,虽然可以使用 then() 里面的第二个函数参数作为发送错误的回调函数;

但是如果只需要失败的回调,除了可以将第一个参数忽略;还可以使用 catch() 方法,catch() 方法不但可以捕获 Promise 实例抛出的错误,then() 运行时发出的错误同样可以捕获。

// 正常情况下,如果只需要失败的回调,可以将第一个参数设置为 null 或是 undefined 即可
new Promise((resolve, reject) => {
    reject('已失败')
}).then(null, error => {
    console.log(error)
})

// 但是上面代码完全可以使用 catch() 进行优化,效果是一样的,但是增加了可读性
new Promise((resolve, reject) => {
    // reject('已失败')
	throw new Error('请求超时')
}).catch(error => {
    console.log(error)
})

4.1 多个 Promise 对象

Promise 的错误具有 "冒泡" 性质,会一直往后传递,知道被捕获为止。

new Promise(resolve => {
  resolve('ok')
})
  .then((value) => {
    // 手动返回一个 rejected 状态
    return new Promise((resolve, reject) => {
      reject('P2')
    })
  })
  .then((value) => {
    console.log(value)
  })
  // 如果上面的 then 都没有设置第二参数 ( rejected 的回调函数 ), 就会'冒泡'到最后的 catch() 进行执行
  .catch((error) => {
    console.log(error)
	// p2
  })

5. Promise.all()

Promise.all() 方法可以一个数组作为参数,其中数组中的元素都是 Promise 实例,如果不是,会自动执行 Promise.resolve() 转换为一个 Promise 实例,再进一步处理。

Promise.all() 提供了并行执行异步操作的能力,会在所有的异步操作执行完毕且状态全为 resolved 才会将自身状态修改为 resolved, 并且把所有 Promise 实例的返回值打包为一个数组。

只要有一个 Promise 实例的状态变为 rejected, Promise.all() 就会直接变为 rejected,并且接收第一个 rejected 实例的返回值作为参数传递出去。

const promise_1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve('第一个异步请求成功了')
    reject('第一个异步请求失败了')
  }, 1000)
})

const promise_2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('第二个异步请求成功了')
  }, 1000)
})

// 以第一个被 reject 的实例的返回值作为 Promose.all 的回到函数的参数
Promise.all([promise_1, promise_2]).then((value) => {
  console.log(value)
  // 第一个异步请求失败了

  // 如果第一个 Promise 实例为成功则返回:['第一个异步请求成功了', '第二个异步请求成功了']
})

5.1 如果 Promise 实例自己捕获了错误呢?

上文说到,如果捕获到 rejected 状态就会直接返回给 Promise.all() 方法,那么作为参数的 Promise 实例身上就自己定义了 catch() 呢?如果定义了,那么就不会触发 Promise.all() 的 catch() 方法。

因为 Promise.catch() 方法返回的也是一个 Promise, 但由于没有主动定义返回状态,所以返回到 Promise.all() 这里自然就是 resolved 状态了,且参数为空。

const promise_1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('第一个异步请求失败了')
  }, 1000)
}).catch((error) => {
  console.log(error)
  // 第一个异步请求失败了
})

const promise_2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('第二个异步请求')
  }, 1000)
})

Promise.all([promise_1, promise_2]).then((value) => {
  console.log(value)
  // [ undefined, '第二个异步请求']
})

6. Promise.allSettled()

上面学习的 Promise.all() 方法,需要所有实例返回 resolved 状态才算成功,只要由一个状态为 rejected 状态就会直接修改状态为 rejected 状态,无论其他操作是否完成。

为了解决这个问题,在 ES11 中新增了 Promise.allSettled() 方法,用来确定一组 Promise 是否都完成了 ( 无论成功与否 )。

同样的,Promise.allSettled() 也是接收一个 Promise 实例组成的数组作为参数,并且返回一个新的 Promise

只是不同的是,Promise.allSettled() 方法只有等到所有的 promise 实例的状态都改变时,返回的 Promise 的状态才会改变。

// 虽然有一个作为参数的 Promise 实例状态为 rejected,但依然返回了数据
const p1 = new Promise((resolve, reject) => {
  reject('失败')
})

const p2 = new Promise((resolve, reject) => {
  resolve('成功')
})

Promise.allSettled([p1, p2]).then((value) => {
  console.log(value)
  // [
  //    {status: 'rejected', reason: '失败'},
  //    {status: 'fulfilled', value: '成功'}
  // ]
})

7. Promise.prototype.race()

Promise.race() 同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

Promise.all() 需要等作为参数所有的 Promise 实例执行完成才会改变状态; 而 Promise.race() 是当作为参数的 Promise 实例有一个先改变状态时 ( 无论成功与否 ), 直接返回给 Promise.race() 了。

7.1 网络请求超时的案例

// p1 为一个网络请求
let p1 = new Promise((resolve, reject) => {
  if(/*请求是否成功*/) {
    resolve()
  }else {
    reject('请求失败')
  }
})

// p2 为一个 2s 的定时器
let p2 = new Promise((resolve, reject) => {
  setTimeout(()=>{
    reject('请求超时')
  }, 2000)
})

// 执行 Promise.race() 方法,只要请求时间超过 2s,就会直接返回请求超时的错误
Promise.race([p1, p2])
  .then((value) => {
    console.log(value)
  })
  .catch((error) => {
    console.log(error)
  })

8. Promise.prototype.finally

Promise.finally() 方法是 ES2018 引入的,用于指定不管 Promise 对象最后状态如何,都会执行的操作

// 封装的 Ajax 请求
function ajax(url) {
  return new Promise((resolve, reject) => {

    // 显示 loading 动画元素
    loading.style.display = 'block'

    let xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.send()
    xhr.onload = function () {
      if (this.status == 200) {
        resolve(JSON.parse(this.response))
      } else {
        reject('加载失败')
      }
    }

    xhr.onerror = function () {
      reject(this)
    }
  })
}

// 请求的 url 地址
let src = 'https://api.apiopen.top/api/sentences'
ajax(src)
  .then((value) => {
   //请求成功打印获取的数据
    console.log(value)
  })
  .finally(() => {
    // 无论成功与否都要隐藏 loading 动画元素
    loading.style.display = 'none'
  })

9. Promise.any()

在 ES2021 中,引入了 Promise.any() 方法,这个方法也是接收一组 Promise 实例作为参数,包装为一个新的 Promise 实例返回。

只要实例中有一个状态改变为 resolved,包装函数就会变为 resolved 状态,如果所有的实例参数都为 rejected 状态,那么包装函数也会变为 rejected 状态。

虽然 Promise.any()Promise.race() 很像,只有一点不同:就是 Promise.any() 不会因为某个 Promise 变为 rejected 状态而结束,必须等到所有参数 promise 都变为 rejected 状态才会结束。

简单来说:

  • Promise.any(): 返回的是第一个状态改变为 resolved,获取先成功的异步操作
  • Promise.race(): 返回的是第一个状态改变的 Promise,获取先完成的异步操作
let p1 = Promise.reject('reject A')
let p2 = Promise.resolve('resolve A')
let p3 = Promise.resolve('resolve B')
let p4 = Promise.reject('reject B')

Promise.any([p1, p2, p3, p4]).then((value) => {
  console.log(value)  // resolve A
})

Promise.race([p1, p2, p3, p4])
  .then((value) => {
    console.log(value)
  })
  .catch((error) => {
    console.log(error)  // reject A
  })

10 总结

  1. Pomise 具有三种状态,pendingresolvedrejected
  2. Promise 可以通过 then 指定成功和失败的回调函数,并且默认返回一个 Promise.resolve()
  3. Promise.resolve()Promise.reject() 其实就是 Promise 的简写形式
  4. Promise.catch() 可以作为前面 promiserejected 状态的回调函数
  5. Promise.all() 可以获取一组 Promise 成功的返回值,或者第一个失败的返回值
  6. Promise.allSettled() 可以获取一组 Promise 的返回数据
  7. Promise.race() 可以获取一组 Promise 中最先完成的异步操作
  8. Promise.finally() 状态改变后就会执行 ( 无论是 resolve 还是 reject )
  9. Promise.any() 可以获取一组 Promise 中最先成功的异步操作
posted @ 2022-10-20 22:47  如是。  阅读(130)  评论(0编辑  收藏  举报