Promise总结

本文深入探讨了Promise的工作原理及其在JavaScript异步编程中的角色,包括状态管理、值穿透、状态跟随特性,以及V8引擎如何优化async/await语法以提升性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

更好的阅度体验

  • 前言
  • API
  • Promise特点
  • 状态跟随
  • V8中的async await和Promise
  • 实现一个Promise
  • 参考

前言

作为一个前端开发,使用了Promise一年多了,一直以来都停留在API的调用阶段,没有很好的去深入。刚好最近阅读了V8团队的一篇***如何实现更快的async await***,借着这个机会整理了Promise的相关理解。文中如有错误,请轻喷~

API

Promise是社区中对于异步的一种解决方案,相对于回调函数和事件机制更直观和容易理解。ES6 将其写进了语言标准,统一了用法,提供了原生的Promise对象。

这里只对API的一些特点做记录,如果需要详细教程,推荐阮老师的Promise对象一文

new Promise
--创建一个promise实例

Promise.prototype.then(resolve, reject)
--then方法返回一个新的Promise实例

Promise.prototype.catch(error)
--.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
--错误会一直传递,直到被catch,如果没有catch,则没有任何反应
--catch返回一个新的Promise实例

Promise.prototype.finally()
--指定不管 Promise 对象最后状态如何,都会执行的操作。
--实现如下:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};
复制代码

Promise.all([promise Array])
--将多个 Promise 实例,包装成一个新的 Promise 实例
--所有子promise执行完成后,才执行all的resolve,参数为所有子promise返回的数组
--某个子promise出错时,执行all的reject,参数为第一个被reject的实例的返回值
--某个子promise自己catch时,不会传递reject给all,因为catch重新返回一个promise实例

Promise.race([promise Array])
--将多个 Promise 实例,包装成一个新的 Promise 实例。
--子promise有一个实例率先改变状态,race的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给race的回调函数。

Promise.resolve()
--将现有对象转为 Promise 对象
--参数是promise实例, 原封不动的返回
--参数是一个thenable对象 将这个对象转为 Promise 对象,状态为resolved
--参数是一个原始值 返回一个新的 Promise 对象,状态为resolved
--不带有任何参数 返回一个resolved状态的 Promise 对象。
--等价于如下代码

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
复制代码

Promise.reject()
--返回一个新的 Promise 实例,该实例的状态为rejected
--Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

Promise特点

很多文章都是把resolve当成fulfilled,本文也是,但本文还有另外一个resolved,指的是该Promise已经被处理,注意两者的区别  
复制代码
1. 对象具有三个状态,分别是pending(进行中)、fulfilled(resolve)(已成功)、reject(已失败),并且对象的状态不受外界改变,只能从pending到fulfilled或者pending到reject。  

2. 一旦状态被改变,就不会再变,任何时候都能得到这个结果,与事件回调不同,事件回调在事件过去后无法再调用函数。  

3. 一个promise一旦resolved,再次resolve/reject将失效。即只能resolved一次。  

4. 值穿透,传给then或者catch的参数为非函数时,会发生穿透(下面有示例代码)  

5. 无法取消,Promise一旦运行,无法取消。  

6. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部  

7. 处于pending时,无法感知promise的状态(刚刚开始还是即将完成)。
复制代码

值穿透代码:

new Promise(resolve=>resolve(8))
  .then()
  .then()
  .then(function foo(value) {
    console.log(value)  // 8
  })
复制代码

状态追随

状态追随的概念和下面的v8处理asyac await相关联

状态跟随就是指将一个promise(代指A)当成另外一个promise(代指B)的resolve参数,即B的状态会追随A。
如下代码所示:

const promiseA = new Promise((resolve) => {
  setTimeout(() => {
    resolve('ccc')
  }, 3000)
})
const promiseB = new Promise(res => {
  res(promiseA)
})
promiseB.then((arg) => {
  console.log(arg) // print 'ccc' after 3000ms
})
复制代码

按理说,promiseB应该是已经处于resolve的状态, 但是依然要3000ms后才打印出我们想要的值, 这难免让人困惑

在ES的标准文档中有这么一句话可以帮助我们理解:

A resolved promise may be pending, fulfilled or rejected.

就是说一个已经处理的promise,他的状态有可能是pending, fulfilled 或者 rejected。 这与我们前面学的不一样啊, resolved了的promise不应该是处于结果状态吗?这确实有点反直觉,结合上面的例子看,当处于状态跟随时,即使promiseB立即被resolved了,但是因为他追随了promiseA的状态,而A的状态则是pending,所以才说处于resolved的promiseB的状态是pending。

再看另外一个例子:

const promiseA = new Promise((resolve) => {
  setTimeout(() => {
    resolve('ccc')
  }, 3000)
})
const promiseB = new Promise(res => {
  setTimeout(() => {
    res(promiseA)
  }, 5000)
})
promiseB.then((arg) => {
  console.log(arg) // print 'ccc' after 5000ms
})
复制代码

其实理解了上面的话,这一段的代码也比较容易理解,只是因为自己之前进了牛角尖,所以特意记录下来:

  1. 3s后 promiseA状态变成resolve
  2. 5s后 promiseB被resolved, 追随promiseA的状态
  3. 因为promiseA的状态为resolve, 所以打印 ccc

V8中的async await和Promise

在进入正题之前,我们可以先看下面这段代码:

const p = Promise.resolve();

(async () => {
  await p;
  console.log("after:await");
})();

p.then(() => {
  console.log("tick:a");
}).then(() => {
  console.log("tick:b");
});
复制代码

V8团队的博客中, 说到这段代码的运行结果有两种:

Node8(错误的):

tick a  
tick b  
after: await
复制代码

Node10(正确的):

after await  
tick a  
tick b
复制代码

ok, 问题来了, 为啥是这个样子?
先从V8对于await的处理说起, 这里引用一张官方博客的图来说明Node8 await的伪代码:

Node8 await

对于上面的例子代码翻译过来就(该代码引用自V8是怎么实现更快的async await)是:

const p = Promise.resolve();

(() => {
  const implicit_promise = new Promise(resolve => {
    const promise = new Promise(res => res(p));
    promise.then(() => {
      console.log("after:await");
      resolve();
    });
  });

  return implicit_promise;
})();

p.then(() => {
  console.log("tick:a");
}).then(() => {
  console.log("tick:b");
});
复制代码

很明显,内部那一句 new Promise(res => res(p)); 代码就是一个状态跟随,即promise追随p的状态,那这跟上面的结果又有什么关系?

在继续深入之前, 我们还需要了解一些概念:

task和microtask

JavaScript 中有 task 和 microtask 的概念。 Task 处理 I/O 和计时器等事件,一次执行一个。 Microtask 为 async/await 和 promise 实现延迟执行,并在每个任务结束时执行。 总是等到 microtasks 队列被清空,事件循环执行才会返回。

如官方提供的一张图:

EnqueueJob、PromiseResolveThenableJob和PromiseReactionJob

EnquequeJob: 存放两种类型的任务, 即PromiseResolveThenableJob和PromiseReactionJob, 并且都是属于microtask类型的任务

PromiseReactionJob: 可以通俗的理解为promise中的回调函数

PromiseResolveThenableJob(promiseToResolve, thenable, then): 创建和 promiseToResolve 关联的 resolve function 和 reject function。以 then 为调用函数,thenable 为this,resolve function和reject function 为参数调用返回。(下面利用代码讲解)

状态跟随的内部

再以之前的代码为例子

const promiseA = new Promise((resolve) => {
  resolve('ccc')
})
const promiseB = new Promise(res => {
  res(promiseA)
})
复制代码

当promiseB被resolved的时候, 也就是将一个promise(代指A)当成另外一个promise(代指B)的resolve参数,会向EnquequeJob插入一个PromiseResolveThenableJob任务,PromiseResolveThenableJob大概做了如下的事情:

() => { 
  promiseA.then(
    resolvePromiseB, 
    rejectPromiseB
  );
}
复制代码

并且当resolvePromiseB执行后, promiseB的状态才变成resolve,也就是B追随A的状态

Node 8中的流程

1. p处于resolve状态,promise调用then被resolved,同时向microtask插入任务PromiseResolveThenableJob  
2. p.then被调用, 向microtask插入任务tickA  
3. 执行PromiseResolveThenableJob, 向microtask插入任务resolvePromise(如上面的promiseA.then(...))  
4. 执行tickA(即输出tick: a),返回一个promise, 向microtask插入任务tickB  
5. 因为microtask的任务还没执行完, 继续  
6. 执行resolvePromise, 此时promise终于变成resolve, 向microtask插入任务'after await'  
7. 执行tickB(即输出tick: b)  
8. 执行'after await'(即输出'after await'复制代码

Node 10 await

老规矩, 先补一张伪代码图:


翻译过来就是酱紫:

const p = Promise.resolve();

(() => {
  const implicit_promise = new Promise(resolve => {
    const promise = Promise.resolve(p)
    promise.then(() => {
      console.log("after:await");
      resolve();
    });
  });

  return implicit_promise;
})();

p.then(() => {
  console.log("tick:a");
}).then(() => {
  console.log("tick:b");
});
复制代码

因为p是一个promise, 然后Promise.resolve会直接将P返回,也就是

p === promise // true
复制代码

因为直接返回了p,所以省去了中间两个microtask任务,并且输出的顺序也变得正常,也就是V8所说的更快的async await

实现一个Promise

先实现一个基础的函数

function Promise(cb) {
  const that = this
  that.value = undefined // Promise的值
  that.status = 'pending' // Promise的状态
  that.resolveArray = [] // resolve函数集合
  that.rejectArray = []  // reject函数集合

  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(function() {
      if (that.status === 'pending') { // 处于pending状态 循环调用
        that.value = value
        that.status = 'resolve'
        for(let i = 0; i < that.resolveArray.length; i++) {
          that.resolveArray[i](value)
        }
      }
    })
  }
  function reject(reason) {
    if (reason instanceof Promise) {
      return reason.then(resolve, reject)
    }
    setTimeout(function() {
      if (that.status === 'pending') { // 处于pending状态 循环调用
        that.value = reason
        that.status = 'reject'
        for(let i = 0; i < that.rejectArray.length; i++) {
          that.rejectArray[i](reason)
        }
      }
    })
  }

  try {
    cb(resolve, reject)
  } catch (e) {
    reject(e)
  }
}
Promise.prototype.then = function(onResolve, onReject) {
  var that = this
  var promise2 // 返回的Promise

  onResolve = typeof onResolve === 'function' ? onResolve : function(v) { return v }  //如果不是函数 则处理穿透值
  onReject = typeof onReject === 'function' ? onReject : function(v) { return v } //如果不是函数 则处理穿透值

  if (that.status === 'resolve') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          const x = onResolve(that.value)
          if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
            x.then(resolve, reject)
          } else {
            resolve(x)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  if (that.status === 'reject') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          const x = onResolve(that.value)
          if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
            x.then(resolve, reject)
          } else {
            reject(x)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  if (that.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {
      that.resolveArray.push(function(value) {
        try {
          var x = onResolve(value)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
      that.rejectArray.push(function(reason) {
        try {
          var x = onReject(reason)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}
Promise.prototype.catch = function(onReject) {
  return this.then(null, onReject)
}
复制代码

参考

v8是怎么实现更快的 await ?深入理解 await 的运行机制
V8中更快的异步函数和promise
剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类
PromiseA+
ES6入门
深入Promise

<think>我们正在处理一个关于JavaScriptPromise的问题。用户希望了解Promise的基本概念和用法。根据提供的引用内容,我们可以总结如下: 引用[1]介绍了PromiseJavaScript中用于管理异步操作的对象,可以避免回调地狱。它有三种状态:pending(初始状态)、fulfilled(操作成功完成)和rejected(操作失败)。状态一旦改变就不可逆转。创建Promise的语法是使用构造函数,传入一个执行函数,该函数有两个参数:resolve(成功时调用)和reject(失败时调用)。 引用[2]同样说明了创建Promise对象的方式,并提到使用.then()处理成功和.catch()处理失败。 引用[3]给出了一个具体的例子:创建一个Promise对象,在setTimeout后调用resolve。根据这些信息,我们可以组织回答如下:1.介绍Promise的基本概念:是什么,为什么需要它(解决回调地狱,管理异步)。 2.解释Promise的三种状态。3.如何创建一个Promise对象。4.如何使用Promise(通过.then()和.catch()方法)。 5.可以给出一个简单的示例代码。 此外,我们还需要按照系统级指令的要求: -行内数学表达式使用$...$(但这个问题中可能没有数学表达式,所以可以忽略)。-独立公式单独成段(同样,可能不需要)。 -使用中文回答。 -生成相关问题。-在回答中引用的段落末尾添加引用标识。由于引用内容来自提供的三个引用,我们在回答中适当添加引用标识。 最后,生成3-5个相关问题。现在,我们开始编写回答。</think>### JavaScript Promise 概念与用法详解 #### 一、Promise 核心概念 PromiseJavaScript 中处理异步操作的标准方案,用于管理需要延迟执行的任务(如网络请求、文件读取等)。它通过状态机机制解决了传统回调函数的"回调地狱"问题,使异步代码更易读和维护[^1]。 **三大状态**: 1. **pending**:初始状态,异步操作未完成 2. **fulfilled**:操作成功完成(调用 `resolve` 触发) 3. **rejected**:操作失败(调用 `reject` 触发) 状态特性: $$ \text{状态转换} \quad \text{pending} \xrightarrow{\text{resolve}} \text{fulfilled} \quad \text{或} \quad \text{pending} \xrightarrow{\text{reject}} \text{rejected} $$ 状态一旦改变便不可逆,且每个 Promise 只有唯一终态[^1]。 #### 二、基本使用方法 ##### 1. 创建 Promise ```javascript const promise = new Promise((resolve, reject) => { // 异步操作(如 API 请求) if (/* 成功条件 */) { resolve('成功数据'); // 状态转为 fulfilled } else { reject('失败原因'); // 状态转为 rejected } }); ``` ##### 2. 处理结果 ```javascript promise .then(result => { console.log('成功:', result); // 处理 fulfilled 状态 }) .catch(error => { console.error('失败:', error); // 处理 rejected 状态 }) .finally(() => { console.log('操作结束'); // 无论成功失败都会执行 }); ``` #### 三、典型使用场景 1. **链式异步操作**(避免回调嵌套): ```javascript fetchData() .then(processData) .then(saveData) .catch(handleError); ``` 2. **并发控制**: ```javascript Promise.all([task1, task2, task3]) // 全部成功才继续 .then(results => /* 处理结果数组 */); Promise.race([task1, task2]) // 首个完成的任务 .then(firstResult => /*...*/); ``` 3. **定时操作**: ```javascript new Promise(resolve => setTimeout(resolve, 2000)) .then(() => console.log('2秒后执行')); ``` #### 四、关键特性 - **链式调用**:`.then()` 始终返回新 Promise,支持连续调用 - **错误穿透**:使用 `.catch()` 可捕获链中任意位置的错误 - **微任务机制**:Promise 回调属于微任务,优先于宏任务(如 `setTimeout`)执行 > 示例:模拟登录流程 ```javascript function login(user) { return new Promise((resolve, reject) => { setTimeout(() => { user === 'admin' ? resolve('登录成功') : reject('账号错误'); }, 1000); }); } login('admin') .then(msg => { console.log(msg); // 输出"登录成功" return '加载用户数据'; // 传递给下一个 then }) .then(data => console.log(data)) // 输出"加载用户数据" .catch(err => console.log(err)); // 捕获错误 ``` [^1]: Promise 的状态管理机制是其异步控制的核心基础 [^2]: 创建语法和执行流程是使用 Promise 的必要知识 [^3]: 链式调用模式显著提升异步代码可维护性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值