Promise介绍及使用

314 阅读4分钟

说明:本文demo的运行环境为node v12.22.12

1. 描述

一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知值的代理。
它让你能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。
这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者
—— 来自MDN

ES6原生支持Promise对象

2.特性

  1. 一个promise必然是以下三种状态
    • pending 未完成(创建初始值)
    • fulfilled 成功
    • rejected 失败
  2. promise的状态转换只能是
    • pending -> fulfilled
    • pending -> rejected
  3. promise的状态只能转换一次,任何时候都能得到这个结果

Xnip2022-06-29_21-07-13.jpg

  • Promise内部的同步代码执行顺序和外部同步代码相同
  • promise已经改变状态后,再次调用方法尝试改变,未生效
  • 两次读取promise,结果相同

3. API

实例方法

我们可以通过Promise.prototype.then、Promise.prototype.catch和Promise.prototype.finally(ES2018)拿到一定完成了的promise

then

  • 参数
    • 第一个参数是promise成功(变为fulfilled)的回调函数
    • 第二个参数是promise失败(变为rejected)的回调函数,可不填写
  • 返回值
    返回一个新的Promise对象,可被用作链式调用

Xnip2022-06-30_09-27-49.jpg

  • then方法中使用return,会返回fulfilled状态的promise,return值就是promise的内容
  • then方法中抛出错误,会返回rejected状态的promise,error则是promise的内容
  • then方法中没有return,也不抛错,效果同return undefined

链式调用,如果有不能处理这个promise的,会继续进行下一个环节。
如下图,第一个then方法没有失败回调不能处理rejected promse,被第二个then的失败回调处理了。 Xnip2022-06-30_10-00-22.jpg

为了保证promise能正常处理,而不抛出程序错误。promise需要有兜底的失败回调。

一种场景是希望不同的promise失败有特殊的处理

promise
    .then(successCallback1, failCallback1)
    .then(successCallback2, failCallback2)
    .then(successCallback3, failCallback3)

另外一种场景是对所有的失败的promise可以一样处理(当然此时也可以只在最后一个then写,但从维护的角度上来看有一点点奇怪),这种情况就可以使用catch方法。

promise
    .then(successCallback1, failCallback)
    .then(successCallback2, failCallback)
    .then(successCallback3, failCallback)

catch

  • 接收一个函数作为参数,函数会在收到rejected promise时被调用
  • 返回一个新的promise,promise的值就是回调函数的返回值
promise
    .then(successCallback1)
    .then(successCallback2)
    .then(successCallback3)
    .catch(failCallback)

Xnip2022-06-30_09-55-17.jpg

Xnip2022-06-30_10-57-36.jpg catch返回的promise的值是回调函数的return值

finally

  • 接收一个函数作为参数,会在promise完成(无论是fulfilled还是rejected)时被调用
  • 返回一个新的promise

Xnip2022-06-30_11-16-39.jpg

  • finally的回调,不能接收原promise的值
  • finally返回的promise的值和原promise相同 注意:finally不能处理Unhandled Promise Rejection Warning错误

静态方法

resolve

返回一个fulfilled状态、给定值的promise

reject

返回一个rejected状态、给定值的promise

Xnip2022-06-30_11-38-12.jpg

all

  • 参数是一个promise iterable(Array,Map,Set都是)
  • 返回一个新的promise,promise值是所有promise resolve组成的数组
  • 新promise的成功回调执行:在所有promise的成功回调都执行完成后
  • 新promise的失败回调执行:只要输入的promise有一个执行了失败回调

Xnip2022-06-30_11-34-05.jpg

race

  • 参数是一个promise iterable(Array,Map,Set都是)
  • 返回一个新的promise, 一旦迭代器后中的某个promise完成后,新的promise也会完成(状态相同)

Xnip2022-07-03_16-20-46.jpg

4. 应用

实现scheduler.add方法,最多同时运行两个任务 (一个有并发限制的调度器)

const timeout = (time) => new Promise(resolve => {
    setTimeout(resolve, time)
  });
  
  const scheduler = new Scheduler();
  const addTask = (time, order) => {
    scheduler
        .add(() => timeout(time))
        .then(() => console.log(order))
  }

/**
 * 开始: 1,2执行
 * 第3.5秒: 2执行完毕,3开始执行
 * 第4秒: 1执行完毕,4开始执行
 * 第7秒: 4执行完毕
 * 第7.5秒: 3执行完毕
 */
  addTask(4000, '1')
  addTask(3500, '2')
  addTask(4000, '3')
  addTask(3000, '4')

思路:

  1. scheduler.add 方法后可以使用.then,说明add的返回值是个promise
  2. 只能两个并发执行,则需要一个队列存储传入的任务(返回值是promise的一个函数)和一个记录当前运行任务(数量)
  3. 任务运行内部应该有递归回调,当前运行任务完成后需要从队列中取出任务持续运行
  4. 每个任务执行完成后,应该有reject或者resolve data

  let start = null;

   class Scheduler{

    constructor(limit = 2) {
      this.limit = limit;// 可并发执行的数量
      this.queue = [];
      this.runningCount = 0;
    }

    add(promiseCreator) {
      return new Promise((resolve, reject) => {
        // 方便内容promise执行后转换外部new Promise的状态
        promiseCreator.resolve = resolve;
        promiseCreator.reject = reject;
        this.queue.push(promiseCreator);
        this.run();
      });
    }

    run() {
      if(this.runningCount >= this.limit) {
        console.log('需要排队');
        return;
      }
      console.log('执行promise');
      this.runningCount++;
      const promiseCreator = this.queue.shift();
      promiseCreator().then(
        (data) => {
          let sec = ((Date.now() - start) / 1000).toFixed(2);
          console.log(`第${sec}秒执行完毕,data 是 ${data}`);
          promiseCreator.resolve(data);
        },
        (error) => {
          promiseCreator.reject(error);
        }
      ).finally(() => {
        this.runningCount--;
        if(this.queue.length > 0){
          this.run();
        }
      });
    }
  }

const timeout = (time) => new Promise(resolve => {
    setTimeout(() => {
      resolve(time);//加了个返回值
    }, time)
});
  
const scheduler = new Scheduler();
const addTask = (time, order) => {
    scheduler
        .add(() => timeout(time))
        .then(() => console.log(order))
}

start = Date.now();
addTask(4000, '1')
addTask(3500, '2')
addTask(4000, '3')
addTask(3000, '4')

Xnip2022-07-03_15-59-31.jpg