Promise

75 阅读13分钟

ES6 Promise源码解析 (从Promise功能的角度看Promise源码实现)

首先,我们再来回顾一下Promise的基本用法,看代码

 new Promise((resolve, reject) => {
     try {
         if (success) {
             resolve()
         }
         if (fail) {
             reject()
         }
     } catch (err) {
         reject(err)
     }
 }).then((res) => {
     console.log(res)
 }, (err) => {
     console.log(err)
 }).then((res) => {
     console.log(res)
 }, (err) => {
     console.log(err)
 }).catch((err => {
     console.log(err)
 }))
 ​
 ​
 ​

我们先从上面的用法上,总结出一些Promise的特点

  • 首先,毫无疑问,Promise()是一个构造函数, 并且,存在3种状态,pending, fulfilled(也可以叫Resolved), rejected。分别表示等待时,成功时,失败时
  • Promise实例化时,传了个参数,并且这个参数是个函数(并且是个立即执行函数),同时这个函数还有两个参数,且这两个参数,依然是函数,分别是resolve(), reject()
  • then()函数中的第一个参数(后文我们统称为then的resolve回调),是在调用then()方法的Promise对象的状态变为fulfilled时被执行的,而第二个参数(后文我们统称为rejected回调),是在Promise对象的状态变成rejected时被调用的。
  • 通过第四代,我们还可以看出,在resolve()函数执行时,是将Promise对象的状态变更成了fulfilled,从而触发了then的resolve回调函数的执行。而reject()函数执行时,是将Promise对象的状态变更成了rejected,从而触发了then的reject回调的执行
  • resolve(res)时的值,就是then的resolve回调的参数,reject(err)的值就是thenreject回调的参数
  • then()函数和catch()函数可以被链式调用

Promise基础雏形

ES6 Promise源码解析 (从Promise功能的角度看Promise源码实现)_查看promise源码-CSDN博客

知晓了他是个构造函数,那我们就创建个构造函数,并定义好状态,并立即执行实例化时传入的函数

 class MyPromise {
     constructor (fun) {
          // 定义初始状态(3个状态分别是pending, fulfilled, rejected)
         this.status = 'pending'
         // 定义两个变量分别来存储成功时值和失败时的值
         this.resolveValue = null
         this.rejectValue = null
 ​
         // 定义resolve函数
         let resolve = () => {}
         
         // 定义reject函数
         let reject = () => {}
         try {
             fun(resolve, reject)
         } catch (err) {
             reject(err)
         }
     }
 }
 ​

此时,构造函数最基本的样子已经有了,定义了一个状态,执行了立即执行函数。并将resolve, reject传入到立即执行函数中。下面我们来完善下 resolve, reject

 let resolve = (val) => {
     // 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
     if (this.status === 'pending') {  // this指向实例化出来Promise对象
         this.status = 'fulfilled'
         // 2、保存resolve时的值,以便后面调用then()方法时使用
         this.resolveValue = val
     }
 }
 ​
 let reject = (val) => {
     // 1、状态变更为rejected
     if (this.status === 'pending') {
         this.status = 'rejected'
         // 2、保存reject()时的值
         this.rejectValue = val
     }
 }
 ​

此时,resolve, reject已经有了,new Promise()时,已经可以调用resolve()方法和reject()方法了。并且,resolve()和reject()时,promise状态也已经发生了改变。并保存了resolve和reject出来的值。

在下一步,状态已经发生改变了,我们是不是要触发then的resolve回调,或者reject回调了。所以,我们来实现then()函数

 MyPromise.prototype.then = (onFullFilled, onRejected) => {
     // onFullFilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
     // 此时,判断状态,不同状态时,分别执行不同的回调
     if (this.status === 'fulfilled') {
         onFullFilled(this.resolveValue)
     }
     if (this.status === 'rejected') {
         onRejected(this.rejectValue )
     }
     // 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
 }
 ​

此时,我们一条执行流程应该是走完了,下面,我们来测试下

 class MyPromise {
     constructor (fun) {
         // 定义初始状态(3个状态分别是pending, fulfilled, rejected)
         this.status = 'pending'
         // 定义两个变量分别来存储成功时值和失败时的值
         this.resolveValue = null
         this.rejectValue = null
 ​
         // 定义resolve函数
         let resolve = (val) => {
             // 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
             if (this.status === 'pending') {  // this指向实例化出来Promise对象
                 this.status = 'fulfilled'
                 // 2、保存resolve时的值,以便后面调用then()方法时使用
                 this.resolveValue = val
             }
         }
         
         // 定义reject函数
         let reject = (val) => {
             // 1、状态变更为rejected
             if (this.status === 'pending') {
                 this.status = 'rejected'
                 // 2、保存reject()时的值
                 this.rejectValue = val
             }
         }
     
         try {
             fun(resolve, reject)
         } catch (err) {
             reject(err)
         }
     }
 }
 ​
 MyPromise.prototype.then = function (onFullFilled, onRejected) {
     // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
     // 此时,判断状态,不同状态时,分别执行不同的回调
     if (this.status === 'fulfilled') {
         onFullFilled(this.resolveValue)
     }
     if (this.status === 'rejected') {
         onRejected(this.rejectValue )
     }
     // 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
 }
 ​
 const promise1 = new MyPromise((resolve, reject) => {
     resolve(123)
 })
 promise1.then((res) => {
     console.log(res)  // 123
 }, (err) => {
     console.log(err)
 })
 ​
 输出了123
 ​
 ​

then

到此时,只是完成了基本,但仍存在很多问题。大家发现没有,我们在then函数中,只判断了状态为fufilled时,调了onFullFilled,状态为rejected时,调了onRejected。但如果then()函数被调用时,promise的状态还并未发生改变(也就是还处于pending时),那then()函数内的代码是不是不会执行拉。因为我们并没有写pending状态时的处理代码。如以下情况

 const promise1 = new MyPromise((resolve, reject) => {
     setTimeout(() => {
         resolve(123)
     }, 0)
 })
 promise1.then((res) => {
     console.log(res)
 }, (err) => {
     console.log(err)
 })
 没有输出
 ​

上面的代码中,立即执行函数内是一个异步代码,此时,是不是先执行了promise1.then(),然后才执行setTimeout中的回调函数啊。所以,此时,当promise1.then()执行时,状态已经是pending,故不会有任何输出。这就有问题了。而我们所希望的,是不是在resolve()或者reject()执行的时候,去触发then()函数的resolve回调或者rejected回调执行啊。 那该怎么做。我先说下思路。在then函数执行时,如果状态还是pending的话,我们是不是可以先把then()的两个回调函数先给他保存起来。然后在resolve()或者reject()的时候,再去触发这个函数的调用呢。我们来写一下 1、先定义两个变量用来保存then()的回调函数

 this.onFullFilledList = []
 this.onRejectedList = []
 ​

2、then()执行时,如果状态还未发生改变(还是pending时),那么就将回调函数先保存起来

 MyPromise.prototype.then = function (onFullFilled, onRejected) {
     // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
     // 此时,判断状态,不同状态时,分别执行不同的回调
     if (this.status === 'fulfilled') {
         onFullFilled(this.resolveValue)
     }
     if (this.status === 'rejected') {
         onRejected(this.rejectValue )
     }
     if (this.status === 'pending') {
         // 保存的是一个函数,而函数内是回调的执行代码,当我们执行被保存的函数时,函数内的onFullFilled和onRejected是不是也就跟着执行拉
         this.onFullFilledList.push(() => {
             onFullFilled(this.resolveValue)
         })
         this.onRejectedList.push(() => {
             onRejected(this.rejectValue )
         })
     }
     // 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
 }
 ​

3、在resolve()和reject()的时候, 去取onFullFilledList,onRejectedList两个队列中的函数,并依次执行

 // 定义resolve函数
  let resolve = (val) => {
      // 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
      if (this.status === 'pending') {  // this指向实例化出来Promise对象
          this.status = 'fulfilled'
          // 2、保存resolve时的值,以便后面调用then()方法时使用
          this.resolveValue = val
 ​
          // 执行then的resolve回调
          this.onFullFilledList.forEach(funItem => funItem())
      }
  }
  
  // 定义reject函数
  let reject = (val) => {
      // 1、状态变更为rejected
      if (this.status === 'pending') {
          this.status = 'rejected'
          // 2、保存reject()时的值
          this.rejectValue = val
 ​
          // 执行then的reject回调
          this.onRejectedList.forEach(funItem => funItem())
      }
  }
 ​

到此,就不会再有因为异步代码而执行不了的问题了。看下完整代码,并验证下

 class MyPromise {
     constructor(fun) {
         // 定义初始状态(3个状态分别是pending, fulfilled, rejected)
         this.status = 'pending'
         // 定义两个变量分别来存储成功时值和失败时的值
         this.resolveValue = null
         this.rejectValue = null
         this.onFullFilledList = []
         this.onRejectedList = []
 ​
         // 定义resolve函数
         let resolve = (val) => {
             // 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
             if (this.status === 'pending') {  // this指向实例化出来Promise对象
                 this.status = 'fulfilled'
                 // 2、保存resolve时的值,以便后面调用then()方法时使用
                 this.resolveValue = val
     
                 // 执行then的resolve回调
                 this.onFullFilledList.forEach(funItem => funItem())
             }
         }
         
         // 定义reject函数
         let reject = (val) => {
             // 1、状态变更为rejected
             if (this.status === 'pending') {
                 this.status = 'rejected'
                 // 2、保存reject()时的值
                 this.rejectValue = val
     
                 // 执行then的reject回调
                 this.onRejectedList.forEach(funItem => funItem())
             }
         }
     
         try {
             fun(resolve, reject)
         } catch (err) {
             reject(err)
         }
 ​
     }
 }
 ​
 MyPromise.prototype.then = function (onFullFilled, onRejected) {
     // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
     // 此时,判断状态,不同状态时,分别执行不同的回调
     if (this.status === 'fulfilled') {
         onFullFilled(this.resolveValue)
     }
     if (this.status === 'rejected') {
         onRejected(this.rejectValue )
     }
     if (this.status === 'pending') {
         this.onFullFilledList.push(() => {
             onFullFilled(this.resolveValue)
         })
         this.onRejectedList.push(() => {
             onRejected(this.rejectValue )
         })
     }
     // 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
 }
 ​
 const promise1 = new MyPromise((resolve, reject) => {
     setTimeout(() => {
         resolve(123)
     }, 0)
 })
 promise1.then((res) => {
     console.log(res)  // 123
 }, (err) => {
     console.log(err)
 })
 输出了123
 ​

微任务队列

执行优先级 Promise 的回调(.then/catch/finally)属于微任务,优先级高于宏任务(如 setTimeout):

 setTimeout(() => console.log('macro'), 0);
 Promise.resolve().then(() => console.log('micro'));
 // 输出顺序:micro → macro

事件循环机制 微任务会在当前宏任务执行完成后立即执行,确保 Promise 的高响应性。

Promise 方法对比表

方法名类型功能描述使用场景示例
.then()实例方法添加成功或失败的回调函数,返回新 Promise,支持链式调用链式异步操作fetch().then(res => res.json())
.catch()实例方法捕获链式调用中的错误(等效于 .then(null, onRejected)全局错误处理fetch().catch(err => console.error(err))
.finally()实例方法无论成功/失败,最终执行的回调(无返回值)清理资源(如隐藏加载动画)fetch().finally(() => hideSpinner())
Promise.all()静态方法所有 Promise 成功时返回结果数组;任意失败立即拒绝并行执行多个独立任务(需全部成功)Promise.all([fetchA(), fetchB()]).then([a, b] => merge(a, b))
Promise.race()静态方法返回第一个 resolved(成功/失败)的 Promise 结果超时控制或竞速请求Promise.race([fetch(), timeout(5000)]).then(first => ...)
Promise.allSettled()静态方法等待所有 Promise 完成,返回包含状态和结果的对象数组记录所有操作的最终结果(无论成功失败)Promise.allSettled([task1(), task2()]).then(results => log(results))
Promise.any()静态方法返回第一个成功的 Promise;全部失败则聚合错误多备用源请求(取首个可用结果)Promise.any([api1(), api2()]).then(data => ...)
Promise.resolve()静态方法快速创建已解决的 Promise(若参数是 Promise,则直接返回)将同步值包装为 PromisePromise.resolve(42).then(v => ...)
Promise.reject()静态方法快速创建已拒绝的 Promise快速抛出错误Promise.reject(new Error('Fail')).catch(err => ...)

关键特性对比

方法成功条件失败条件返回值结构
Promise.all()全部成功任意一个失败结果数组(按输入顺序)
Promise.race()第一个 resolved(成功/失败)第一个 rejected第一个 resolved 的结果
Promise.allSettled()不关心成功/失败,全部完成对象数组(含 statusvalue
Promise.any()任意一个成功全部失败第一个成功结果(或 AggregateError

使用场景速查

  • 并行任务,需全部成功Promise.all()
  • 竞速或超时控制Promise.race()
  • 记录所有任务最终状态Promise.allSettled()
  • 多备用请求,取首个成功Promise.any()
  • 链式操作与错误统一处理.then() + .catch()

如果需要更详细的参数说明或兼容性信息,可参考 MDN Promise 文档。

async awaie 会阻塞代码执行吗

  1. 首先,回顾async/await的基本概念。async函数返回一个Promise,而await用于等待Promise的解决。用户可能认为await会像同步代码那样阻塞整个线程,但实际上JavaScript是单线程的,async/await并不会阻塞事件循环。
  2. 接下来,需要明确“阻塞”在JavaScript中的含义。同步代码会阻塞主线程,直到操作完成,比如循环或长时间计算。而异步操作(如setTimeout、Promise)会将任务放入任务队列,主线程继续执行后续代码。async/await本质上是基于Promise的语法糖,所以其行为应与Promise类似,即非阻塞。
  3. 然后,考虑用户可能的误区。用户可能在看到await时,误以为后面的代码会等待,导致整个程序停止。需要解释虽然await会暂停async函数的执行,但不会阻塞主线程,其他任务(如事件处理、定时器)仍可继续执行。
  4. 举例说明,比如在async函数中使用await调用一个网络请求,函数内会暂停,直到请求完成,但函数外的代码可以继续执行。同时,需要提到事件循环的机制,如何将异步任务处理到微任务队列中,从而不影响主线程。
  5. 还要对比同步阻塞和异步非阻塞的区别,帮助用户理解为什么async/await不会导致阻塞。可能用代码示例来展示两者的不同行为,比如同步等待与await的区别。
  6. 另外,需要指出常见的误解,比如在async函数中有多个await时,每个await都会按顺序执行,但整个函数仍是异步的,不会阻塞其他代码。比如在一个async函数中,两次await调用接口,第二个会等第一个完成,但页面UI仍可响应。
  7. 最后,总结关键点:async/await不会阻塞主线程,只是暂停函数内部的执行,通过事件循环机制让出主线程,保持应用响应。同时提醒用户避免在async函数中执行长时间同步代码,这样才会真正阻塞。

async/await 不会阻塞 JavaScript 的主线程代码执行,但它会暂停当前 async 函数的内部执行,直到 await 的 Promise 完成。这是异步非阻塞的关键特性。


核心原理

  1. 主线程不受阻 当遇到 await 时:

    • 暂停 async 函数内后续代码的执行(函数内部“看似”阻塞)。
    • 释放主线程,让事件循环继续处理其他任务(如 UI 渲染、定时器、网络请求等)。
  2. 基于事件循环的机制

    • await 后的代码会被包装成微任务,在 Promise 完成后加入微任务队列。
    • 主线程空闲时,事件循环会优先执行微任务,恢复 async 函数的执行。

代码示例

 async function fetchData() {
   console.log("开始请求数据");
   const data = await fetch('https://api.example.com'); // 暂停函数内部,释放主线程
   console.log("数据获取完成:", data); // 微任务中恢复执行
 }
 ​
 console.log("脚本启动");
 fetchData();
 console.log("脚本继续执行"); // 主线程立即执行,无需等待 fetchData 完成

输出顺序

 脚本启动
 开始请求数据
 脚本继续执行
 (网络请求完成后)数据获取完成: ...

执行流程示意图

 async function demo() {
   console.log(1);
   await console.log(2); // 2 是同步代码,立即执行,但 await 会等待其返回值(undefined)被解析
   console.log(3); // 此处代码会被包装成微任务,主线程空闲后执行
 }
 ​
 console.log('start');
 demo();
 console.log('end');
 ​
 // 输出顺序:
 // 'start' → 1 → 2 → 'end' → 3
 输出顺序的问题是由于JavaScript的异步执行机制导致的。
 让我解释一下代码执行过程:
 首先执行 console.log('start')
 然后调用 demo() 函数:
 函数内先执行 console.log(1)
 遇到 await console.log(2),这里有个问题:console.log(2) 不是一个返回Promise的函数,但JavaScript会自动将它包装成一个已解决的Promise
 执行 console.log(2) 后,由于await,函数暂停执行,控制权返回到主线程
 主线程继续执行,打印 console.log('end')
 当主线程任务执行完毕后,事件循环会回到之前暂停的异步操作,继续执行 demo() 函数中的 console.log(3)
 所以输出顺序是: start → 12 → end → 3
 这是因为在异步函数中使用 await 后的代码会被放入微任务队列(microtask queue),只有当主线程执行完毕后才会执行这些微任务。