Skip to content

Promise ​

Promise 规范 ​

Promise 是一套专门处理异步场景的规范,它能有效的避免回调地狱的产生,使异步代码更加清晰、简洁、统一。

这套规范最早诞生于前端社区,规范名称为 Promise A+。

该规范出现后,立即得到了很多开发者的响应。

Promise A+ 规定:

  1. 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在 JS 中应该表现为一个 对象,该对象称之为 Promise 对象,也叫做任务对象。

    alt

  2. 每个任务对象,都应该有两个阶段、三个状态。

    alt

    根据常理,它们之间存在以下逻辑:

    • 任务总是从未决阶段变到已决阶段,无法逆行。
    • 任务总是从挂起状态变到完成或失败状态,无法逆行。
    • 时间不能倒流,历史不可改写,任务一旦完成或失败,状态就固定下来,永远无法改变。
  3. 挂起 -> 完成,称之为 resolve;挂起 -> 失败 称之为 reject。任务完成时,可能有一个相关数据;任务失败时,可能有一个失败原因。

    alt

  4. 可以针对任务进行后续处理,针对完成状态的后续处理称之为 onFulfilled,针对失败的后续处理称之为 onRejected。

    alt

Promise API ​

ES6 提供了一套 API,实现了 Promise A+ 规范。

基本使用如下:

JavaScript
// 创建一个任务对象,该任务立即进入 pending 状态
const pro = new Promise((resolve, reject) => {
  // 任务的具体执行流程,该函数会立即被执行
  // 调用 resolve(data),可将任务变为 fulfilled 状态, data 为需要传递的相关数据
  // 调用 reject(reason),可将任务变为 rejected 状态,reason 为需要传递的失败原因
});

pro.then(
  (data) => {
    // onFulfilled 函数,当任务完成后,会自动运行该函数,data 为任务完成的相关数据
  },
  (reason) => {
    // onRejected 函数,当任务失败后,会自动运行该函数,reason 为任务失败的相关原因
  }
);

至此,回调地狱的问题仍然没能解决。

要解决回调地狱,还需要进一步学习 Promise 的知识。

catch 方法 ​

.catch(onRejected) = .then(null, onRejected)

链式调用 ​

image-20210621103501094

  1. then 方法必定会返回一个新的 Promise。

    可理解为 后续处理也是一个任务。

  2. 新任务的状态取决于后续处理:

    • 若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的数据。

    • 若有后续处理但还未执行,新任务挂起。

    • 若后续处理执行了,则根据后续处理的情况确定新任务的状态:

      • 后续处理执行无错,新任务的状态为完成,数据为后续处理的返回值。
      • 后续处理执行有错,新任务的状态为失败,数据为异常对象。
      • 后续执行后返回的是一个任务对象,新任务的状态和数据与该任务对象一致。

由于链式任务的存在,异步代码拥有了更强的表达力。

JavaScript
// 常见任务处理代码

/*
 * 任务成功后,执行处理1,失败则执行处理2
 */
pro.then(处理1).catch(处理2);

/*
 * 任务成功后,依次执行处理1、处理2
 */
pro.then(处理1).then(处理2);

/*
 * 任务成功后,依次执行处理1、处理2,若任务失败或前面的处理有错,执行处理3
 */
pro.then(处理1).then(处理2).catch(处理3);

Promise 的静态方法 ​

方法名含义
Promise.resolve(data)直接返回一个完成状态的任务
Promise.reject(reason)直接返回一个拒绝状态的任务
Promise.all(任务数组)返回一个任务
任务数组全部成功则成功
任何一个失败则失败
Promise.any(任务数组)返回一个任务
任务数组任一成功则成功
任务全部失败则失败
Promise.allSettled(任务数组)返回一个任务
任务数组全部已决则成功
该任务不会失败
Promise.race(任务数组)返回一个任务
任务数组任一已决则已决,状态和其一致

消除回调 ​

有了 Promise,异步任务就有了一种统一的处理方式。

有了统一的处理方式,ES 官方就可以对其进一步优化。

ES2017 推出了两个关键字 async 和 await,用于更加优雅的表达 Promise。

async ​

async 关键字用于修饰函数,被它修饰的函数,一定返回 Promise。

JavaScript
async function method1() {
  return 1; // 该函数的返回值是 Promise 完成后的数据
}

method1(); // Promise { 1 }

async function method2() {
  return Promise.resolve(1);
  // 若返回的是 Promise,则 method 得到的 Promise 状态和其一致
}

method2(); // Promise { 1 }

async function method3() {
  throw new Error(1); // 若执行过程报错,则任务是 rejected
}

method3(); // Promise { <rejected> Error(1) }

await ​

await 关键字表示等待某个 Promise 完成,它必须用于 async 函数中。

JavaScript
async function method() {
  const n = await Promise.resolve(1);
  console.log(n); // 1
}

// 上面的函数等同于
function method() {
  return new Promise((resolve, reject) => {
    Promise.resolve(1).then((n) => {
      console.log(n);
      resolve(1);
    });
  });
}

await 也可以等待其他数据:

JavaScript
async function method() {
  const n = await 1; // 等同于 await Promise.resolve(1)
}

如果需要针对失败的任务进行处理,可以使用 try-catch 语法:

JavaScript
async function method() {
  try {
    const n = await Promise.reject(123); // 这句代码将抛出异常
    console.log('成功', n);
  } catch (err) {
    console.log('失败', err);
  }
}

method(); // 输出: 失败 123

面试题 ​

  1. 下面代码的输出结果是什么?

    JavaScript
    const promise = new Promise((resolve, reject) => {
      console.log(1);
      resolve();
      console.log(2);
    });
    
    promise.then(() => {
      console.log(3);
    });
    
    console.log(4);
  2. 下面代码的输出结果是什么?

    JavaScript
    const promise = new Promise((resolve, reject) => {
      console.log(1);
      setTimeout(() => {
        console.log(2);
        resolve();
        console.log(3);
      });
    });
    
    promise.then(() => {
      console.log(4);
    });
    
    console.log(5);
  3. 下面代码的输出结果是什么?

    JavaScript
    const promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve();
      }, 1000);
    });
    const promise2 = promise1.catch(() => {
      return 2;
    });
    
    console.log('promise1', promise1);
    console.log('promise2', promise2);
    
    setTimeout(() => {
      console.log('promise1', promise1);
      console.log('promise2', promise2);
    }, 2000);
  4. 下面代码的输出结果是什么?

    JavaScript
    async function m() {
      const n = await 1;
      console.log(n);
    }
    
    m();
    console.log(2);
  5. 下面代码的输出结果是什么?

    JavaScript
    async function m() {
      const n = await 1;
      console.log(n);
    }
    
    (async () => {
      await m();
      console.log(2);
    })();
    
    console.log(3);
  6. 下面代码的输出结果是什么?

    JavaScript
    async function m1() {
      return 1;
    }
    
    async function m2() {
      const n = await m1();
      console.log(n);
      return 2;
    }
    
    async function m3() {
      const n = m2();
      console.log(n);
      return 3;
    }
    
    m3().then((n) => {
      console.log(n);
    });
    
    m3();
    
    console.log(4);
  7. 下面代码的输出结果是什么?

    JavaScript
    Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);
  8. 下面代码的输出结果是什么?

    JavaScript
    var a;
    var b = new Promise((resolve, reject) => {
      console.log('promise1');
      setTimeout(() => {
        resolve();
      }, 1000);
    })
      .then(() => {
        console.log('promise2');
      })
      .then(() => {
        console.log('promise3');
      })
      .then(() => {
        console.log('promise4');
      });
    
    a = new Promise(async (resolve, reject) => {
      console.log(a);
      await b;
      console.log(a);
      console.log('after1');
      await a;
      resolve(true);
      console.log('after2');
    });
    
    console.log('end');
  9. 下面代码的输出结果是什么?

    JavaScript
    async function async1() {
      console.log('async1 start');
      await async2();
      console.log('async1 end');
    }
    async function async2() {
      console.log('async2');
    }
    
    console.log('script start');
    
    setTimeout(function () {
      console.log('setTimeout');
    }, 0);
    
    async1();
    
    new Promise(function (resolve) {
      console.log('promise1');
      resolve();
    }).then(function () {
      console.log('promise2');
    });
    console.log('script end');
参考答案
  1. 1 👉 2 👉 4 👉 3
  2. 1 👉 5 👉 2 👉 3 👉 4
  3. promise { < pending > } 👉 promise { < pending > } 👉 promise { < undefined > } 👉 promise { < undefined > }
  4. 2 👉 1
  5. 3 👉 1 👉 2
  6. promise { < pending > } 👉 promise { < pending > } 👉 4 👉 1 👉 3 👉 1
  7. 1
  8. promise1 👉 undefined 👉 end 👉 promise2 👉 promise3 👉 promise4 👉 promise { < pending > } 👉 after1
  9. script start 👉 async1 start 👉 async2 👉 promise1 👉 script end 👉 async1 end 👉 promise2 👉 setTimeout