文章目录
- 为什么引入Promise?
- [1.promise 的基本认识](#1.promise 的基本认识)
- 2.promise的API
-
- [2.1promise 构造/创建类()](#2.1promise 构造/创建类())
- [2.2 实例方法-promise.then/.catch](#2.2 实例方法-promise.then/.catch)
- [2.3 Promise 组合类 APi](#2.3 Promise 组合类 APi)
-
- [2.3.1 promise.race()](#2.3.1 promise.race())
- [2.3.2 Promise.all()](#2.3.2 Promise.all())
- [3. async/await](#3. async/await)
-
- [3.1 async/await 是什么?](#3.1 async/await 是什么?)
- [3.2 async语法](#3.2 async语法)
- [3.3 await语法](#3.3 await语法)
- [3.4 如何使用 async/await?](#3.4 如何使用 async/await?)
为什么引入Promise?
JavaScript 有一个重要的概念------异步(async),它允许我们在执行运行任务时,不一定等待进程完成,而是继续执行下面的代码,直到任务完成再通知。常用的异步操作有:文件操作、数据库操作、AJAX 以及定时器等。
JavaScript 有两种实现异步的方式:
第一种:callback函式 回调函数
在 ES6 Promise 出现之前,通常使用回调函数式(callback)实现异步操作。但使用回调函数式回调存在一个明显的缺点,当需要执行多个异步操作方案时,代码会不断往内调用,这种情况通常被称为「回调地狱」(callback hell)。
所以为了解决此类问题,就出现了第二种方法 - Promise。
1.promise 的基本认识
Promise是用来表示一个异步操作的最终完成(或失败)及其结果值
Promise的三种状态
- 待定状态(pending) :初始状态,既没有被兑现,也没有被拒绝
- 已兑现(fulfilled):意味着,操作成功完成
- 已拒绝(rejected):意味着,操作失败

根据ES Spec 标准,Promise 是一个 带内部槽(internal slots)的对象
他的一个最小抽象的模型如下:状态与结果值
Promise {
[[PromiseState]]: "fulfilled",
[[PromiseResult]]: 32(一个值)
}
比如打印一个 promise 对象,就能看到这两个内容槽,其他的内容槽对于开发调试没什么作用,不展示

拓展(便于理解):一个 promise (抽象)大致有这些内部槽
txt
[[PromiseState]] // pending / fulfilled / rejected
[[PromiseResult]] // value 或 reason
[[PromiseFulfillReactions]] // then 成功回调队列
[[PromiseRejectReactions]] // then/catch 失败回调队列
[[PromiseIsHandled]] // 是否已处理 rejection
2.promise的API
2.1promise 构造/创建类()
用来创建或得到 Promise
| API | 作用 |
|---|---|
| new Promise(executor) | 创建一个可手动控制状态的 Promise |
| Promise.resolve(value) | 把任意值转换为 fulfilled Promise |
| Promise.reject(reason) | 创建一个 rejected Promise |
Promise(executor)的框架,同步执行
javascript
new Promise((resolve, reject) => {}
- 调用 resolve()函数,会让 promise 对象的状态从 pending
Promise.resolve(value)
作用:把任何值 x 转换成一个 Promise
- 如果 x 本来就是 Promise → 原样"接管"
- 如果 x 是普通值 → 包装成 fulfilled Promise
返回值:Promise
2.2 实例方法-promise.then/.catch
| API | 作用 |
|---|---|
| .then() | 处理 fulfilled |
| .catch() | 处理 rejected |
| .finally() | 不关心结果,做清理 |
.then 本身并不会立刻执行回调,
而是等待上一个 Promise 状态确定:
若 Promise 变为 fulfilled(被 resolve),则执行 .then 中的 onFulfilled 回调
若 Promise 变为 rejected(被 reject),则跳过 onFulfilled,失败状态继续向下传播
这里的 fulfilled / rejected 仅指 Promise 状态,与 HTTP 请求是否成功、业务是否成功无关。
.then() 一定会返回一个新的 Promise。这是 Promise 链能一直"接着写"的根本原因。
.then 的完整语法其实是:
javascript
promise.then(onFulfilled, onRejected)
所以
json
.then(
(res) => {
console.log(res);
},
(reason) => {
console.log(reason);
}
);
| 位置 | 什么时候执行 | 参数来自哪里 |
|---|---|---|
| 第一个函数 | Promise 成功 | resolve(value) |
| 第二个函数 | Promise 失败 | reject(reason) |
.catch 的完整语法其实是:
promise.then(undefined, onRejected)
那其实 promise本质就是一直链式调用.then,为什么只要上面有一步.then 出错就报了 catch?
Promise 规范里还有一条非常关键的规则:Promise 链的"状态传播规则"
如果一个 .then 的回调没有被执行(因为 Promise 是 rejected),
那么这个 .then 会"原样返回一个 rejected 的 Promise"。
来看一个例子
p
.then(A)
.then(B)
.then(C)
.catch(D)
内部等价于:
p1 = p.then(A)
p2 = p1.then(B)
p3 = p2.then(C)
p4 = p3.then(undefined, D)
-
假如 a 抛出了错误,p1 变成了 rejected
-
B 不会执行,但是p2 仍然是 rejected
-
同理 c 也是,所以到 D 这,D 执行
2.3 Promise 组合类 APi
2.3.1 promise.race()
-
Promise.race 会返回一个新的 Promise
-
采用最先完成的那个 Promise 的状态和值
用 promise 内部槽的观点来理解,Promise.race 会创建一个新的 Promise,并在内部采用(adopt)最先 settle 的Promise 的[[PromiseState]] 与 [[PromiseResult]],而不继承其余内部槽。
settle 是指不是 pedding 状态
手写 promiseRace
javascript
function promiseRace(promises) {
return new Promise((resolve, reject) => {
for (const p of promises) {
p.then((val) => {
resolve(val);
}).catch((e) => {
reject(e);
});
}
});
}
- 首先明确(resolve, reject) => {和resolve(val);/ reject(e);的关系
- new Promise((resolve, reject) => {}) 中的 resolve 和 reject,是用来用来改变这个 Promise 状态的控制函数,当调用 resolve(value) 时,Promise 从 pending 变为 fulfilled,并将 value 作为结果传递给后续的 .then 回调。
- 其中的resolve(val) 会将 val 写入新 Promise 的 [[PromiseResult]];这里的 val来源于外部 Promise 在其 fulfilled 时传入的值
- promiseRace(promises),表示这个外部函数是多个 promise,for (const p of promises),同步遍历所有 Promise,给它们注册 .then / .catch 回调,然后将最快的返回赋值给新的 promise
外部示例如下:
javascript
const p1 = new Promise(r => setTimeout(() => r('A'), 1000));
const p2 = new Promise(r => setTimeout(() => r('B'), 500));
const p3 = new Promise(r => setTimeout(() => r('C'), 1500));
const raceP = promiseRace([p1, p2, p3]);
- 只执行一次、只吃到 'B'。
2.3.2 Promise.all()
Promise.all 是什么?
Promise.all 接收一组 Promise 的Iterable(可遍历对象,例如Array、Map、Set),返回一个新的 Promise;
只有当所有 Promise 都 fulfilled 时,新 Promise 才 fulfilled,
并且结果是一个按顺序排列的结果数组;
只要有一个 Promise rejected,整体立刻 rejected。
-
Promise.all 返回的结果数组顺序,严格等于"传入数组的顺序",与各个 Promise 实际完成(执行)顺序无关。
-
Promise.all 的参数不要求"全是 Promise 对象",它会先对每一项执行 Promise.resolve(x),
所以普通值(如 42)会被当成"已完成的 Promise"
-
定义中有提到,如果输入为空,例如空数组,就返回一个空数组
常见使用场景:
- 多接口并发请求,全部成功再渲染
- 页面初始化依赖多个异步资源
- 批量任务统一完成后再继续
来看一个小例子:
javascript
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "foo");
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// 预期输出结果: Array [3, 42, "foo"]
补充:setTimeout 的写法:
| 写法 | 发生时间 | 结果 |
|---|---|---|
| setTimeout(resolve, 100, "foo") | 100ms 后 | resolve("foo") |
| setTimeout(() => resolve("foo"), 100) | 100ms 后 | resolve("foo") |
| setTimeout(resolve("foo"), 100) | 立刻 | Promise 立刻 resolve(❌) |
手写 promise.all,简单实现 promise.all,只考虑 Iterable 是数组的情况
javascript
function promiseAll(promises)
{
// 如果参数不是数组,返回一个 JS 的类型错误
if(!Array.isArray(promises))
{
return new TypeError("参数必须是一个数组");
}
//如果输入是空数组,就返回空数组,根据定义
if(promises.length===0)
{
return Promise.resolve([]);
}
//定义一个输出结果和计数器记录数组长度,把最终结果存在这里,开始核心的逻辑
const outputs=[];
let resolveCounter=0;
//1.promise.all最终要返回一个新的 promise 对象,最外层应该是 promise executor,resolve的返回值应该是 outputs数组
//2.遍历外部的 promises,拿到 promises 中的每一个 promise
//3.每一个 promise 对象的处理
// (1)因为参数不保证一定是 promise 对象,所以应该将参数处理成 promise对象
// (2)调用.then,如果这个 promise 对象fulfilled,会给他传入一个参数 value,将这个结果加入到 outputs 数组
// (3)如果这个 promise 对象 rejected,直接结束整个函数,因为 promise.all是如果有一个失败,那么直接算失败
return new Promise((resolve,reject)=>{
promises.forEach((promise,index)=>{
Promise.resolve(promise)
.then((val)=>{
outputs[index]=val;
resolveCounter++;
if(resolveCounter==promises.length)
{
resolve(outputs);
}
})
.catch(reject);
});
});
}
(完整版)如果代码要支持 Iterable:
核心思路(只加 2 行)
- 把传入的 Iterable 转成数组
- 后面的逻辑继续按数组处理
javascript
// 1. 参数必须是 iterable
if (promises == null || typeof promises[Symbol.iterator] !== 'function') {
throw new TypeError("参数必须是一个可迭代对象");
}
// 2.最小关键改动:把 iterable 转成数组
const list = Array.from(promises);
3. async/await
3.1 async/await 是什么?
在 JavaScript 中,async/await是一种让异步(非同步)操作更容易理解和管理的语法。它建立在 Promise 的基础上,但提供了更简洁、更直观的方式来处理异步操作。
3.2 async语法
使用async关键字声明的函数式为异步函数式,异步函数式会返回一个 Promise 对象,而不直接返回函数式执行的结果。让我们通过示例来了解:
- 下方普通函式
f1()直接返回字串"Hello! ExplainThis!"
javascript
// 普通函式
function f1() {
return "Hello! ExplainThis!";
}
f1(); // 輸出: "Hello! ExplainThis!"
- 加上 async
javascript
// 异步函数
async function f2() {
return "Hello! ExplainThis!";
}
f2(); // 输出: Promise {<fulfilled>: 'Hello! ExplainThis!'}

- 由于
async函式总是返回一个 Promise 对象,如果要获取该 Promise 的解析值,可以使用.then()方法:
javascript
async function f2() {
return "Hello! ExplainThis!";
}
f2().then((result) => {
console.log(result); // "Hello! ExplainThis!"
});
3.3 await语法
await是一个关键字,用于等待一个承诺完成或拒绝。它通常与async函式一起使用,因为只有在async函式内部或模组的配件,才能使用await。
使用await时,程序会暂停执行该async函式,直到await等待的 Promise 完成并回传结果后,才会继续往下执行。让我们透过下面的示例来了解:
javascript
async function getData() {
// await 等待 fetch 这个非同步函数返回一个 Promise 并解析它
const res = await fetch("https://example.com/data");
// await 等待上一步的 Promise 解析后,再解析它的 JSON 资料
const data = await res.json();
// 前面两步都完成后,才会执行这一行并打印出资料
console.log(data);
}
getData();
- 需要注意的是
await等待的 Promise 完成并回传结果后,其实是拿到 promise 对象的[[PromiseResult]] ,就比如 fetch就是得到一个 promise 对象,await 后拿到他的[[PromiseResult]]
使用 await 要注意的几点
-
在非 async 函数中使用 await 会报 SyntaxError 的错误
javascriptfunction f() { let promise = Promise.resolve("Hello! ExplainThis!"); let result = await promise; } // Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules -
top-level await:
- ES Module(ESM)是 JavaScript 官方的模块系统,用 import / export,有文件级作用域,支持静态分析,是现在前端和 Node 的标准模块方案。
在"没有顶层 await"的年代,模块里想拿异步数据,只能用 Promise + then 的绕法:
javascript//config.js import getData from "./getData.js"; let data; getData().then((result) => { data = result; // ...使用data }); export const dataPromise = getData(); // main.js import { dataPromise } from "./config.js"; const data = await dataPromise; console.log(data);-
import getData from "./getData.js";从另一个模块引入一个函数 getData.其中getData 是一个返回 Promise 的异步函数
-
let data;先声明一个变量,此时data 还是 undefined
-
调用异步函数,等 Promise resolve 之后,才把结果塞进 data
模块加载时我没法直接 await,只能先声明一个变量,再等异步请求回来之后,在 then 里"补上"这个变量。
模块加载完成时,data 并不是可用状态,模块"已经被别人 import 使用了",但数据还没准备好
顶层 await 的时候
javascript//config.js const data = await getData(); export { data }; // main.js import { data } from './config.js'; console.log(data); // ✅ 一定是已经准备好的数据- 不用再包一层 async 函数,模块在加载阶段会等待 await 完成后再继续执行依赖模块
- config.js 在对外可用之前,必须先把 data 拉取完成。别的文件引入即可用
- config.js 是一个 异步初始化模块
3.4 如何使用 async/await?
使用 async/await 可以将异步代码写成类似同步的形式,使其更易读、且更易维护。让我们先看一个使用 Promise 写的 getData 函数例子:
先来看用 Promise 来写一个 getData 函式的例子:
javascript
function getData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((res) => res.json())
.then((data) => resolve(data))
.catch((error) => reject(error));
});
}
getData("https://example.com/data")
.then((data) => console.log(data))
.catch((error) => console.error(error));
在这个例子中,getData 函式使用 Promise 来处理异步操作。我们需要使用 .then() 和 .catch() 方法来获取结果或错误。
现在,我们使用 async/await 来重写 getData 函式:
javascript
async function getData(url) {
try {
const res = await fetch(url);
const data = await res.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
getData("https://example.com/data");
- 使用
async关键字定义一个异步函式,该函式会返回一个 Promise 对象。 - 在异步函式中,使用
await等待 Promise 的完成,并直接返回结果。 - 使用
try...catch捕获错误,使得错误处理更加方便和直观。 - 可以看到,使用
async/await后,代码变得更加清晰和易于理解。
async/await 与 Promise 的差别?
async/await 和 Promise 都是用于处理异步操作的方式,但它们有以下一些差异:
- 语法:
async/await提供了更简洁、更直观的语法,使得异步代码更易读和维护。Promise 则需要使用then和catch方法来处理结果和错误,语法上较为冗长。 - 错误处理: 在
async/await中,可以直接使用try...catch来捕获错误,而在 Promise 中需要使用catch方法。 - 代码流程:
async/await可以使异步代码看起来更像同步代码,更容易阅读和理解。Promise 的代码流程则较为不连贯