Promise 总结记录

207 阅读5分钟

promise 特性

1. Promise 构造函数是同步执行的,promise.then 中的函数(也就是回调函数)是异步执行的。
const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)
// 结果是:1,2,4,3
2. promise 有 3 种状态:pending、fulfilled 或 rejected。状态改变只能是 pending->fulfilled 或者 pending->rejected,状态一旦改变则不能再变
const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})

promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
// then: success1
3. promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用
Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })
// 1, 2
4. promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})

const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log(res, Date.now() - start)
})
// once
// success 1005
// success 1007
5. .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获
Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
// then: Error: error!!!
// at Promise.resolve.then (...)
// at ...

返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))

所以要返回一个被 catch 捕获的错误,需要用 reject / throw new Error('error!!!')来返回

return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')

特别提示:async 也遵循这个规则,不难理解,async 也是返回了一个 promise。

async function q() {
  return new Error('Errorrrr')
};
q().then(res => console.log('then:', res));
/*then: Error: Errorrrr
    at q (<anonymous>:1:28)
    at <anonymous>:1:51
    Promise{<resolve>: undefined}
*/    

async function q() {
  throw new Error('Errorrrr')
};
q().then(res =>  console.log('then:', res));
// Promise {<rejected>: Error: Errorrrr
// VM6544:1 Uncaught (in promise) Error: Errorrrr
6. .then.catch 返回的值不能是 promise 本身,否则会造成死循环
const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)
/*
TypeError: Chaining cycle detected for promise #<Promise>
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:667:11)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:607:3
*/
7. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
// 1
8. 链式调用顺序

链式调用的一些关键知识点:

  • 如果前面的 promise 已经是 resolved 状态,则会立即将回调推入微任务队列(但是执行回调还是要等到所有同步任务都结束后),如果没有同步任务,就会逐个取出再执行
  • 如果前面的 promise 是 pending 状态则会将回调存储在 promise 的内部,一直等到 promise 被 resolve 才将回调推入微任务队列/执行
  • 对于 then 方法返回的 promise 它是没有 resolve 函数的,取而代之只要 then 中回调的代码执行完毕并获得同步返回值,这个 then 返回的 promise 就算被 resolve。假如 (promise1)then 中的回调返回了一个 promise(内部promise2),那么根据第3点可知 .then 需要等promise(内部promise2) resolve后才会返回一个promise

来看一段代码

new Promise((resolve, reject) => {
  // 同步代码,第一个打印 log 1
  console.log("log: 外部promise");
  // 将两个 .then 推入微任务队列,此时没有同步任务,取出第一个 .then 执行
  resolve();
})
  .then(() => {
  	// 同步代码 log2
    console.log("log: 外部第一个then");
    new Promise((resolve, reject) => {
      // 同步代码 log 3
      console.log("log: 内部promise");
      // 将第二个 Promise 的 .then 推入微任务队列,且执行第一个 .then
      resolve();
    })
      .then(() => {
      	// 同步代码 log 4
        // 此时,第二个Promise resolve了,第一个 Promise 的第一个 .then 返回了一个新的 Promise 供链式调用
        console.log("log: 内部第一个then");
      })
      .then(() => {
      	// 第一个 Promise 的 .then 全部执行完毕了,执行微任务中剩下的第二个Promise 的第二个 .then
      	// log 6
        console.log("log: 内部第二个then");
      });
  })
  .then(() => {
  	// 第一个 Promise 的 .then 执行完毕,现在执行第二个 .then
    // log 5
    console.log("log: 外部第二个then");
  });
// log: 外部promise
// log: 外部第一个then
// log: 内部promise
// log: 内部第一个then
// log: 外部第二个then
// log: 内部第二个then
9. async,await

async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。

先看一段代码

async function async1() {
  cosnole.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2');
}
console.log('script start');
async1();
new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
})
console.log('script end');

如果不了解 async,await的运行机制的话,上面的代码的输出将会很难理解。

已知 async 函数会返回一个 Promise 对象,那么在 这个 Promise resolve 之前,await 之后的代码不会执行,如

async function async1() {
  cosnole.log('async1 start');
  await async2();
  console.log('async1 end');
}
// 效果等同于以下代码
async function async1() {
  cosnole.log('async1 start');
  new Promise(resolve => resolve(async2());).then(() => {
    console.log('async1 end');
  })
}

这样看起来,就很熟悉了吧。所以以上的代码可转化为这样:

async function async1() {
  // log 2
  cosnole.log('async1 start');
  new Promise(resolve => resolve(async2());).then(() => {
    // log 6
    console.log('async1 end');
  })
}

async function async2() {
  // log 3
  console.log('async2');
}
// log 1
console.log('script start');
setTimeout(function() {
  // log 8
  console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
  // log 4
  console.log('promise1');
  resolve();
}).then(function() {
  // log 7
  console.log('promise2');
})
// log 5
console.log('script end');
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise 2
// settimeout

本文的代码及部分结论均来自参考文章中。用于自己记录总结所用。具体的内容,请看参考文章。

参考

Promise 链式调用顺序引发的思考

Promise 必知必会(十道题)