Promise对象认知|8月更文挑战

246 阅读8分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

Promise的含义

  • Promise是异步编程的一种解决方案,主要解决了传统回调函数的回调地狱问题。
  • es6 将Promise写进了语言标准,统一了用法,原生提供了 Promise 对象

特点

  • Promise对象的状态不受外界影响。三种状态:
    • pending(进行中)
    • fulfilled(已成功
    • rejected(已失败)
  • 一旦状态改变,就不会再变,任何时候都能得到这个结果。
    • 状态只能从 pedding 变为fulfilled,或者从pedding 变为rejected

缺点

  • 一旦新建Promise它就会立即执行,无法中途取消。(也就是说 new Promise()会立即执行,此处会涉及到EventLoop的面试考题)
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于pedding状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

基本用法

  • Promise对象是一个构造函数,用来生成Promise实例。
  • Promise构造函数接收一个函数作为参数
  • 而作为参数的这个函数又接收两个参数 resolvereject
  • resolvereject也是两个函数,由javaScript引擎,不用自己部署。 知道了Prmise构造函数的这些特点,那么就可以尝试实例化一个promise
const promise = new Promise((resolve, reject) => {
    if(/*异步操作成功*/) {
        resolve(value)
    } else {
        reject(error)
    }
})
  • resolve 函数的作用:,将Promise对象的状态从pedding变为resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
  • reject函数的作用:将Promise对象的状态 从 pedding 变为 rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
  • Promise实例生成之后,可以使用其原型链上的then方法,分别指定 resolved状态和rejected状态的回调函数。
  • then方法接收两个参数,分别是成功的回调和失败的回调。成功的回调和失败的回调函数都接收Promise对象传出的值作为参数。(也就是我们在new Promise时 resolve和reject方法通过参数抛出的值)

promise.then((value) => {
    // 此方法是成功的回调
    console.log(value)
}, (error) => {
    // 此方法是失败的回调
    console.log(error)
})

注意的点

  • 如果一个promise的resolve接收了另一个promise作为参数,即一个异步操作的结果是返回另一个异步操作。如下所示:
const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

将p1作为参数传递给p2,此时p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

上述代码中,正常来说p2在1秒之后会执行resolve,将状态变为resolved,但是我们可以看到p2的回调函数中resolve接收了p1作为参数,此时p2的状态就不是它自己说了算的,而是决定于p1的状态,而p1此时还处于pedding状态,那么此刻的p2需要等待p1的状态变更,再2秒之后p1调用了reject方法,状态变成了rejected,此时p2的回调函数立即执行,状态变成了rejectd,调用catch最终输出fail。

  • resolve或者reject执行之后并不会终结Promise的参数函数的执行。也就是说,resolve()之后的代码依旧会执行,并且要先于resolve()输出结果.
  • resolved的Promise是在本轮事件循环的末尾执行。
  • 通常建议以下写法:
new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
})

Promise.prototype.then()

  • then方法是定义在原型对象Promise.prototype上的
  • then方法的作用是为Promise实例添加状态改变时的回调函数。
  • then方法接收两个可选参数:
    1. 成功的回调函数
    2. 失败的回调函数
  • then方法 返回的是一个 新的Promise实例。所以可以进行链式调用。

Promise.prototype.catch()

  • 同样的catch也是定义在原型对象Promise.prototype上的。
  • Promise.prototype.catch()是 .then(null, rejection) 或 .then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
  • then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
  • Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
  • 如果没有使用catch()方法指定错误处理的回调函数,Promise对象抛出的错误是不会传递到外层代码的,既不会有任何反应。
  • Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。 看下面的例子:
const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123

上述代码中,最终promise内部报错之后 后续代码还是会输出123,可见 Promise 内部的错误不会影响到 Promise 外部的代码。

Promise.prototype.finally()

  • 同样的finally也是定义在原型对象Promise.prototype上的。
  • finally()方法用于指定不管Promise对象最后的状态如何,都会执行的操作。也就是说finally方法总是会执行。
  • finally方法的回调函数不接受任何参数。
  • finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
  • finally本质上是then方法的特例。

简单实现 finally

Promise.prototype.finally = function (callback) {
    let P = this.constructor;
    return this.then(
        value => P.resolve(callback()).then(() => value),
        reason => P.resolve(callback()).then(() => {throw reason})
    )
}

Promise.all()

  • Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
  • Promise.all() 接收一个数组作为参数。也可以不是数组,但是必须具有Iterator接口,且返回的每个成员都是Promise实例。
  • 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
  • 如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

Promise.race()

  • Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
  • 只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
  • Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。

Promise.allSettled()

  • Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
  • 该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected
  • 状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的 Promise 实例。

Promise.any()

  • Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
  • 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
  • Promise.any()抛出的错误,不是一个一般的 Error 错误对象,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。

Promise.resolve()

  • Promise.resolve()用于将现有对象转为Promise对象。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
  • Promise.resolve()方法的参数分成四种情况:
    1. 参数是一个Promise实例:如果参数是 Promise 实例,Promise.resolve将不做任何修改、原封不动地返回这个实例。
    2. 参数是一个thnable对象:(thenable对象指的是具有then方法的对象),Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。
    3. 参数不是具有then()方法的对象,或根本不是对象:如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved
    4. 不带任何参数Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

Promise.reject()

  • Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected
  • Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

本文参考自:es6入门