Skip to content
大纲

promise 基础

在开始正式的之前,我们先思考两个问题:

  • promise 的出现主要是为了解决什么问题?
    • 让一个异步操作以外部可判断、可控制的的形式返回执行结果,队列形式解决多重回调
  • promise 在什么场合被运用了?被广泛运用的原因?

Promise 的状态一经改变就不能再改变。(见 3.1) .then 和.catch 都会返回一个新的 Promise。(上面的 👆1.4 证明了) catch 不管被连接到哪里,都能捕获上层未捕捉过的错误。(见 3.2) 在 Promise 中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如 return 2 会被包装为 return Promise.resolve(2)。

js
// 除了下面两种
return Promise.reject(new Error('error!!!'))
// or
throw new Error('error!!!')

Promise 的 .then 或者 .catch 可以被调用多次,但如果 Promise 内部的状态一经改变,并且有了一个值,那么后续每次调用.then 或者.catch 的时候都会直接拿到该值。(见 3.5) .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。 .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环

js
const promise = Promise.resolve().then(() => {
  return promise
})
promise.catch(console.err)

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。

js
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)

.then 方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为 catch 是.then 第二个参数的简便写法。(见 3.9) .finally 方法也是返回一个 Promise,他在 Promise 结束的时候,无论结果为 resolved 还是 rejected,都会执行里面的回调函数。

先来小题几道

js
// 构造函数是同步执行,then 方法是异步执行,只有等到 resolve 函数执行时才得到执行
const promise = new Promise((resolve, reject) => {
  // 执行同步代码
  console.log('1')
  // 将 promise 的状态改为了 resolved 并且将值保存下来
  resolve()
  console.log('2')
  // 跳出 promise
})
// 遇到 promise.then 微任务,加入微任务队列
promise.then(() => {
  console.log('3')
})
// 执行同步代码
console.log('4')
  • Promise 在生命周期内有三种状态,分别是 pending、fulfilled 或 rejected,状态改变只能是 pending->fulfilled(成功),或者 pending->rejected(失败)。而且状态一旦改变,就不能再次改变。
  • Promise 的构造函数中代码是同步执行的,但是 then 方法是异步执行的,then 方法需要等到等到 resolve 函数执行时才得到执行
js
const promise = new Promise((resolve, reject) => {
  resolve('1')
  reject('error')
  resolve('2')
})
promise
  .then((res) => {
    console.log('then:', res)
  })
  .catch((err) => {
    console.log('catch:', err)
  })
  • Promise.resolve() 返回一个 resolved 状态的 promise 实例,所以后面的 then 方法可以执行,then 方法的返回值,作为下一个 then 方法的参数,如果后面跟着的是 catch,如果 catch 后是否还有 then 方法,则跳过 catch 方法向下执行 then 方法
js
Promise.resolve(1)
  .then((res) => {
    console.log('then1:', res)
    return 2
  })
  .then((res) => {
    console.log('then2:', res)
    return 3
  })
  .catch((err) => {
    console.log('catch:', err)
    return 4
  })
  .then((res) => {
    console.log('then3:', res)
    return 5
  })
  • 前一个 then 方法的返回值,会作为后一个的参数
js
Promise.resolve()
  .then(() => {
    console.log(0)
    return Promise.resolve(4) // 返回对象是 thenable,所以又有异常微任务
  })
  .then((res) => {
    console.log(res)
  })
  • Promise 中包含计时器,Promise 构造函数,先创建了一个宏任务,宏任务执行时,改变 promise 的状态
  • 后面遇到已经改变状态的 promise 实例的.then 方法则将其添加到本轮中的微任务列表,promise 微任务
js
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('1')
    // 保存结果并将之前的 promise.then 推入微任务队列
    resolve('success')
  }, 1000)
})
const start = Date.now()
promise
  .then((res) => {
    console.log(res, Date.now() - start)
  })
  .then((res) => {
    console.log(res, Date.now() - start)
  })
  • 异步方法 then 报错,不影响前面 then 的状态,每一个 then 方法都返回一个新的 promise 实例,then 方法报错,可以被后续的 catch 方法捕获,但不能被前面的 catch 方法捕获,所有可以使用 catch 静态方法进行捕获
js
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('1')
  }, 1000)
})
const promise2 = promise.then((res) => {
  throw new Error('2')
})
// pending 状态
console.log('promise:', promise)
console.log('promise2', promise2)
setTimeout(() => {
  // fulfilled 状态
  console.log('promise:', promise)
  // rejected 状态
  console.log('promise2', promise2)
}, 2000)
  • 两个 Promise 实例的 then 链微任务交替执行是按就近原则
javascript
new Promise((resolve, reject) => {
  console.log('1') // 1. Promise 构造函数接受的参数是一个需要立即执行的函数,是一个同步任务
  resolve()
})
  .then(() => {
    // 2. 注册 then 方法,把它加到微任务队列
    // 3. 没有同步代码,开始执行该微任务
    console.log('2')
    new Promise((resolve, reject) => {
      // 4. 继续执行 Promise 构造函数
      console.log('3')
      resolve()
    })
      .then(() => {
        // 5. 注册其 then 方法,将其加到微任务队列
        console.log('4') // 7. 执行
      })
      .then(() => {
        // 8. 注册
        console.log('5') // 10. 执行
      })
  })
  .then(() => {
    // 6. 没有同步代码,第一个 then 执行完毕,继续注册外层 Promise 的第二个 then
    console.log('6') // 9. 执行
  })
  • 有 return 语句时的就近原则,需要将 return 表达式执行完毕,才能执行外层第二个 then
js
new Promise((resolve, reject) => {
  console.log('1') // 1. 构造函数的参数,先执行
  resolve()
})
  // 2. 注册 then,添加微任务,没有同步任务,执行其微任务
  .then(() => {
    console.log('2') // 3. 执行第一个 then
    // 看到 return,需要将表达式执行完毕,才能执行外层第二个 then
    return (
      new Promise((resolve, reject) => {
        console.log('3') // 4. 构造函数执行
        resolve()
      })
        // 5. 注册 then, 添加微任务
        .then(() => {
          console.log('4') // 6. 执行
        })
        .then(() => {
          // 7. 注册
          console.log('5') // 8. 执行
        })
    )
  })
  .then(() => {
    // 9. 注册
    console.log('6') // 10. 执行
  })

注:

  • Promise 状态不可逆
  • 值穿透:前面的 then 没有添加回调,后面 then 的回调拿到 resolve 的参数值
  • Promise.resolve('param') 直接从 pending 到 resolved,后面加 then 回调本身也会返回一个新的 promise 对象,所以也可以根据需要给 then 回调返回包含逻辑的 promise 对象,其 resolve(val) 值可以作为后续的 then 的参数,前一个状态有后一个状态决定
  • 任意一个非 promise 的值都会被包裹成 promise 对象,例如 return 2 会被包装为 return Promise.resolve(2)。
  • catch 后的 then 中的 res 得到的就是 catch 中的返回值
js
// 任意一个非 promise 的值都会被包裹成 promise 对象,所以此处也被包裹成了 return Promise.resolve(new Error('error!!!'))
Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
  • .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
js
const promise = Promise.resolve().then(() => {
  return promise
})
promise.catch(console.err)
  • .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传
js
// 第一个 then 和第二个 then 中传入的都不是函数,因此发生了透传,将 resolve(1) 的值直接传到最后一个 then 里。
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)

Promise.all() 的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。 .race() 的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。 Promise.all().then() 结果中数组的顺序和 Promise.all() 接收到的数组顺序一致。 all 和 race 传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被 then 的第二个参数或者后面的 catch 捕获;但并不会影响数组中其它的异步任务的执行。

.finally

  • .finally 会等 promise.then() 执行完才会将 finally() 加入微任务队列
  • .finally 不管 Promise 对象最后的状态如何都会执行
  • .finally() 方法的回调函数不接受任何的参数
  • .finally 返回的默认会是一个上一次的 Promise 对象值,不过如果抛出的是一个异常则返回异常的 Promise 对象。
  • .finally 它的回调函数是接收不到 Promise 的结果的
js
Promise.resolve('1')
  .then((res) => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
    return '我是 finally2 返回的值'
  })
  .then((res) => {
    console.log('finally2 后面的 then 函数', res)
  })

Promise.resolve('1')
  .finally(() => {
    console.log('finally1')
    throw new Error('我是 finally 中抛出的异常')
  })
  .then((res) => {
    console.log('finally 后面的 then 函数', res)
  })
  .catch((err) => {
    console.log('捕获错误', err)
  })

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('resolve3')
    console.log('timer1')
  }, 0)
  resolve('resovle1')
  resolve('resolve2')
})
  .then((res) => {
    console.log(res)
    setTimeout(() => {
      // 打印的事 finally 的返回值,.finally 的返回值如果在没有抛出错误的情况下默认会是上一个 Promise 的返回值
      console.log(p1)
    }, 1000)
  })
  .finally((res) => {
    console.log('finally', res)
  })

Promise.all 和 Promise.race

  • Promise.all() 的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
  • Promise.race() 的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
  • Promise.all 和 Promise.race 传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被 then 的第二个参数或者后面的 catch 捕获;但并不会影响数组中其它的异步任务的执行
js
function runAsync(x) {
  const p = new Promise((r) =>
    setTimeout(() => r(x, console.log(x)), 1000)
  )
  return p
}
js
async function async1() {
  console.log('async1 start')
  // 碰到了 await,它会阻塞 async1 后面代码的执行,因此会先去执行 async2 中的同步代码 async2,然后跳出 async1
  // 可以理解为「紧跟着 await 后面的语句相当于放到了 new Promise 中,下一行及之后的语句相当于放在 Promise.then 中」。
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
async1()
console.log('start')