前言

Promise 主要是为解决程序异步处理而生的,在现在的前端应用中无处不在,已然成为前端开发中最重要的技能点之一。它不仅解决了以前回调函数地狱嵌套的痛点,更重要的是它提供了更完整、更强大的异步解决方案。当然,Promise 也是前端面试中必不可少的考察点,考察内容可深可浅,因此熟练掌握它是每个前端开发者的必备能力

Promise基本介绍

首先,Promise也是一个类或者说是一个构造函数,由JS原生提供,通过对Promise实例化后完成一些预期的异步任务。
Promise 接受异步任务并立即执行,然后在任务完成后,将状态标注成最终结果(成功或失败)。

Promise 有三种状态:初始化时,刚开始执行主体任务,这时它的初始状态时 pending(进行中);等到任务执行完成,这时根据成功或失败,分别对应状态 fulfilled(成功)rejected(失败),这时的状态就固定不能被改变了,即 Promise 状态是不可逆的

基本用法

和平常使用类一样,我们要使用的话就new一个Promise对象就可以了

const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('errors')
    }, 2000)
})
console.log(' I am running');
myPromise.then((res) => {
    console.log(res);
})

上面创建好 Promise 实例后,里面的主体会立即执行,比如,如果是发送XHR请求,则会立即把请求发出去,如果是定时器,则会立即启动计时。我们这里使用的setTimeout定时器。至于请求什么时候返回,我们就在返回成功的地方,通过 resolve() 将状态标注为成功即可,同时 resolve(data) 可以附带着返回数据,然后在 then() 里面进行回调处理。

当初始化 Promise 实例时,主体代码是同步就开始执行了的,只有 then() 里面的回调处理才是异步的,因为它需要等待主体任务执行结束。技能考察时常常会通过分析执行顺序考察此处

const myPromise = new Promise((resolve, reject) => {
  // 这里是 Promise 主体,执行异步任务
  console.log(1);
    setTimeout(() => {
        resolve('errors')
    }, 2000)
}).then(() => {
  console.log(2);
})
console.log(3);// 最终输出 1、3、2

如果我们在调用 then() 之前,Promise 主体里的异步任务已经执行完了,即 Promise 的状态已经标注为成功了。**
那么我们调用 then 的时候,并不会错过,还是会执行。但需要记着,即使主体的异步任务早就执行完了,then() 里面的回调永远是放到微任务里面异步执行的,而不是立马执行。**

比如我们在主体里面仅执行一块同步代码,从而不需要等待,下面代码 then() 将依然最后输出。因此我们也常常利用这种方式构建微任务(相对应的利用 setTimeout 构建宏任务):

const myPromise = new Promise((resolve, reject) => {
    // 主体只有同步代码,则 Promise 状态会立马标注为成功
    console.log(1);
    resolve();
  }).then(() => {
    console.log(2);
  })
  console.log(3);
  // 最终输出为 1、3、2

异常处理

  • 方法1:
    then()的第二个参数

    const myPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
          reject('errors')
      }, 2000)
    })
    console.log(' I am running');
    myPromise.then((res) => {
      console.log(res);
    }, (err) => {
      console.log(err);
      
    })

    这种方式能捕获到 promise 主体里面的异常,并执行 errorCallback 代码块。但是如果 Promise 主体里面没有异常,然后进入到 successCallback 里面发生了异常,此时将不会进入到 errorCallback 代码块。因此我们经常使用下面的方式二来处理异常。

  • 方法2:
    catch()里捕获错误

    const myPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
          reject('errors')
      }, 2000)
    })
    console.log(' I am running');
    myPromise.then((res) => {
      console.log(res);
    }).catch((err) => {
      console.log(err);
    })

    这样不管是 Promise 主体,还是 successCallback 里面的出了异常,都会进入到 errorCallback。

  • 方法3:
    try...catch
    try catch 是传统的异常捕获方式,这里只能捕获同步代码的异常,并不能捕获异步异常,因此无法对 Promise 进行完整的异常捕获。

链式调用

Promise 的链式调用,每次调用后,会返回一个新的 Promise 实例对象,从而可以继续 then()或者其他 API 调用,如上面的方式二异常处理中的 catch 就属于链式调用。

const myPromise = new Promise((resolve) => {
  resolve(1)
}).then((data) => {
  return data + 1;
})).then((data) => {
  console.log(data)
};
// 输出 2

需要注意的是,每次then()或者catch()之后,返回的都是一个新的Promise,和上一次的 Promise 实例对象已经不是同一个引用了。而这个新的 Promise 实例对象包含了上一次 then 里面的结果,这也是为什么链式调用的 catch 才能捕获到上一次 then 里面的异常的原因。

常用的API

Promise API 和大部分类一样,分为实例 API 或原型方法(即 new 出来的对象上的方法),和静态 API 或类方法(即直接通过类名调用,不需要 new)。注意实例 API 都是可以通过链式调用的

实例方法(原型方法)

  • then()
    Promise 主体任务和在此之前的链式调用里的回调任务都成功的时候(即前面通过 resolve 标注状态后),进入本次 then() 回调。
  • catch()
    Promise 主体任务和在此之前的链式调用里的出现了异常,并且在此之前未被捕获的时候(即前面通过 reject 标注状态或者出现 JS 原生报错没处理的时候),进入本次 catch()回调。
  • finally()
    无论前面出现成功还是失败,最终都会执行这个方法(如果添加过)。比如某个任务无论成功还是失败,我们都希望能告诉用户任务已经执行结束了,就可以使用 finally()

静态API(类方法)

  • Promise.resolve()
    返回一个成功状态的Promise实例,一般用于构建微任务,比如有个耗时操作,我们不希望阻塞主程序,就把它放到微任务去,如下输出 1、3、2,即 console.log(2) 将放到最后微任务去执行:

    console.log(1);
    Promise.resolve().then(() => {
    console.log(2); // 作为微任务输出 2
    })
    console.log(3);
  • Promise.reject()
    与resolve类似,返回一个失败的Promise实例
  • Promise.all()
    此方法接收一个数组作为参数(准确说是可迭代参数),数组里的每一项都是一个单独的Promise实例,此方法返回一个Promise对象,这个返回的对象含义是数组中所有 Promise 都返回了(可失败可成功),返回 Promise 对象就算完成了。适用于需要并发执行任务时,比如同时发送多个请求。

    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
          resolve('1s after')
      }, 1000)
    })
    
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
          resolve('2s after')
      }, 2000)
    })
    
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
          resolve('3s after')
      }, 3000)
    })
    
    Promise.all([p1, p2, p3]).then((res) => {
      //  数组里都成功了就会来到这里
      console.log(res);
    }).catch((err) => {
      //  有一个失败了就会来到这里
      console.log(err);
    })

注意 Promise.all 是所有传入的值都返回状态了,才会最终进入 then 或 catch 回调。

特别提醒一点,Promise.all()内部的参数也可以写入常量,等价于:

Promise.all([1, 2, 3]);

// 等同于
const p1 = new Promise(resolve => resolve(1));
const p2 = new Promise(resolve => resolve(2));
const p3 = new Promise(resolve => resolve(3));
Promise.all([p1, p2, p3]);
  • Promise.race()
    与 Promise.all() 类似,不过区别是 Promise.race 只要传入的 Promise 对象,有一个状态变化了,就会立即结束,而不会等待其他 Promise 对象返回。所以一般用于竞速的场景。
最后修改:2023 年 02 月 22 日
收款不要了,给孩子补充点点赞数吧