中文: 请你解释一下什么是 Promise?它主要解决了什么问题?
English: Can you explain what a Promise is and what problem it is designed to solve?
标准答案(中文)
Promise 是一个用于表示异步操作结果的对象,它本质是一个状态机 + 回调容器。 它代表一个未来会完成(或失败)的操作结果。
Promise 主要解决的是:
Promise 有三种状态:
一旦状态从 pending 变为 fulfilled 或 rejected,就不可再改变(状态不可逆)。
作用:Promise能够让你不用关心任务什么时候结束(因为用到Promise的地方,可能都会有异步操作),在new的时候告诉它执行什么任务,之后只需要通过 then 告诉它“之前的任务结束之后该干嘛”。
面试加分点:Promise 本质上是一个状态机,并且它的回调(then/catch)是放入微任务队列执行的。
Standard Answer (English)
A Promise is an object representing the result of an asynchronous operation, essentially a state machine + callback container. It represents the eventual completion or failure of an operation and its resulting value.
A Promise mainly solves:
A Promise has three states:
Once the state changes from pending to fulfilled or rejected, it becomes immutable (cannot be changed again).
面试加分点:A Promise is essentially a state machine, and its callbacks (then/catch) are executed in the microtask queue.
中文: Promise 的 then 方法返回的是什么?为什么可以链式调用?
English:
What does the then method return, and why can Promises be chained?
标准答案(中文)
then 方法一定会返回一个新的 Promise 对象。
之所以可以链式调用,是因为:
then 都会返回一个新的 Promisethen 会接收上一个 Promise 的结果具体规则:
then 里返回的是一个普通值:
→ 会被包装成 Promise.resolve(value)then 会等待这个 Promise 结果Promise.reject(error),进入 catch面试加分点:
then 的链式调用本质是“值的穿透 + Promise 的扁平化(flattening)”。
Standard Answer (English)
The then method always returns a new Promise.
Chaining works because:
then call returns a new Promisethen receives the result of the previous oneRules:
Promise.resolve(value)then waits for itPromise.reject(error) and goes to catch面试加分点:
Promise chaining is essentially value propagation + Promise flattening.
举个面试常用例子
Promise.resolve(1) .then(res => res + 1) .then(res => { return Promise.resolve(res + 1); }) .then(res => { throw new Error("error"); }) .catch(err => { console.log(err); });你可以这样解释:
中文:
English:
中文:
then 的两个参数(onFulfilled, onRejected)和 catch 有什么区别?为什么通常不推荐在 then 里写第二个参数?
English:
What is the difference between the two arguments of then (onFulfilled, onRejected) and catch? Why is it usually not recommended to use the second argument of then?
分析:
x// 示例1:then 的第二个参数无法捕获前面 onFulfilled 的错误Promise.resolve() .then(() => { throw new Error('在成功回调里出错啦'); // ← 这里抛错 }, (err) => { console.log('onRejected:', err); // ← 不会执行! });// 示例2:catch 可以捕获Promise.resolve() .then(() => { throw new Error('在成功回调里出错啦'); }) .catch((err) => { console.log('catch 捕获到:', err); // ← 可以捕获 });推荐写法:
xxxxxxxxxx// 推荐:分离成功和失败,代码更清晰promise .then((value) => { // 成功处理 }) .catch((err) => { // 失败处理 }) .finally(() => { // 无论成功失败都执行 });中文回答: then 的两个参数(onFulfilled, onRejected)和 catch 有什么区别?
then(onFulfilled, onRejected) 和 .catch(onRejected) 功能相关,但有重要区别:
最关键的区别在于错误捕获范围:
推荐写法: 成功用 .then(),错误统一用 .catch(),代码更清晰,可读性更好。
中文回答:为什么不推荐用 then 的第二个参数?
因为它:
then 内部抛出的错误推荐统一使用 catch 来处理错误,使代码更清晰、可维护。
面试加分: Promise 的错误是“冒泡”的,应该在链的末尾统一用 catch 处理。
Standard Answer (English)
then(onFulfilled, onRejected) and .catch(onRejected) are related but have key differences:
The most important difference is error handling scope:
Best practice: Use .then() only for success, and chain .catch() for error handling. It improves readability and reduces bugs.
Why not use the second argument of then?
English:
It is not recommended because:
thenUsing catch provides a more consistent and maintainable error handling pattern.
面试加分:
Errors in Promises bubble down the chain and should be handled at the end using catch.
中文: 如果在 then 里不写 return,会发生什么?值是怎么传递的?
English:
What happens if you don’t return anything inside a then? How is the value propagated?
这个问题我感觉很陌生
Promise的then方法,我记得最多只用了一层,就是获取数据之后的处理,没有仔细的研究过then的链式用法。一般这种情况我会使用async...await方法来处理需要从前一个异步操作获取数据的场景。
一般来说,我是这样做的:
xxxxxxxxxxfunction getUser() {return axios.get('https://jsonplaceholder.typicode.com/users/1');}// 调用getUser().then(res => {console.log('封装后拿到数据:', res.data);results = res.data;}).catch(err => console.log(err));好像连
new Promise定义promise过程都没有用到。所以最多使用只有一层then的调用。如果是一个接口请求需要前一个接口的返回结果,我一般是这么写的:
xxxxxxxxxx// 现在项目都这么写const fetchData = async () => {const res1 = await api1()const res2 = await api2(res1.id)const res3 = await api3(res2.name)}这样做其实是对的,因为相对于多个then方法,async...await有以下好处:
- 比一堆
.then()可读性强太多,不用记 return、不用关心值透传。- 框架本身封装了请求库、全局拦截、封装成统一
async函数,直接 await 就行。- 多人协作约定:禁止深层 then 链式,统一用 async/await。
但是必须要理解then的原理,因为老项目或者面试的时候肯定会问到:
必须懂 then 链式的理由
- 看懂老项目代码
- 面试必考 Promise 链式、值传递、不 return 的行为
- 理解 async/await 底层就是语法糖,本质还是 Promise + then
then的用法
实际开发中,几乎不用 then 的第二个参数,统一用
.catch()捕获所有错误,更清晰。
then()会返回新的 Promise,所以可以无限链式写多个 then,按顺序执行异步任务:xxxxxxxxxx// 封装一个模拟请求数据的函数function getUserData() {return new Promise((resolve) => {// 模拟网络请求延时setTimeout(() => {// 拿到原始后台数据const rawData = {id: 1,name: "张三",age: 20};// 把数据传给 thenresolve(rawData);}, 1000);});}// 使用 Promise + then 操作数据getUserData().then(data => {// 在这里自由操作、加工、处理数据console.log("原始数据:", data);// 1. 解构赋值const { id, name, age } = data;// 2. 数据加工const newUser = {userId: id,userName: name,isAdult: age >= 18};console.log("加工后的数据:", newUser);});链式规则:
then里return 普通值,会直接传给下一个thenthen里return 新 Promise,下一个then会等待这个 Promise 完成- 任何一步报错,都会直接跳到最后的
catchreturn到底是哪里的return呢?
then函数里面,有两个函数参数,return应该写在哪里呢?
- 这是两个独立的函数,各自都可以写
return,互不干扰。- 哪个函数被执行,你就在哪个函数里写 return
- 不管是成功回调 return、还是失败回调 return。都会包装成新 Promise,传给下一个 then
- 只要不是抛错 / 返回失败 Promise,下一个then一律走成功分支。
所以
日常开发只用
then(成功回调).catch(失败),很少用 then 第二个参数。使用catch来捕捉错误即可。
标准答案(中文)
如果在 then 里面不写 return,那么:
👉 默认会返回 undefined
👉 等价于:return undefined
并且这个 undefined 会被自动包装成:
xxxxxxxxxxPromise.resolve(undefined)然后传递给下一个 then
面试加分:
then 的返回值决定了下一个 then 的输入,如果不写 return,默认就是 undefined。
Standard Answer (English)
If you don’t return anything inside a then:
👉 It implicitly returns undefined
👉 Equivalent to: return undefined
This undefined is wrapped as:
xxxxxxxxxxPromise.resolve(undefined)and passed to the next then.
面试加分:
The return value of then determines the input of the next then. If omitted, it defaults to undefined.
几个例子,看返回值是什么?
例子1:
xxxxxxxxxxPromise.resolve(1) .then(res => { console.log(res); // 1 }) .then(res => { console.log(res); // ? });输出是什么?
xxxxxxxxxx1undefined
为什么?
中文:
then 没有 returnundefinedthen 接收到的就是 undefinedEnglish:
then does not return anythingundefinedthen receives undefined例子2:
xxxxxxxxxx.then(() => 123)等价于:
xxxxxxxxxx.then(() => { return 123;})返回了123
例子3:
xxxxxxxxxx.then(() => { Promise.resolve(123);})⚠️ 注意!!!
这里没有 return,所以下一个 then 仍然拿到 undefined。
中文: Promise.resolve() 和 new Promise(resolve => resolve()) 有什么区别?
English:
What is the difference between Promise.resolve() and new Promise(resolve => resolve())?
这一题已经进入Promise + 事件循环(event loop)核心考点了。
标准答案(中文)
Promise.resolve() 和 new Promise(resolve => resolve()) 都会返回一个已解决(fulfilled)的 Promise,但它们在执行时机和行为细节上是有区别的。
执行时机:
new Promise 里的函数是同步执行的Promise.resolve() 是直接创建一个已完成的 Promise行为细节:
Promise.resolve 会自动“展开”(flatten)Promise
如果你给
Promise.resolve(xxx)传的xxx本身就是一个 Promise,它不会包一层 Promise 再套一层,而是直接 “拆开”,等待内部那个 Promise 执行完,直接用它的结果。这就叫 flatten / 展开 / 扁平化。
Standard Answer (English)
Both Promise.resolve() and new Promise(resolve => resolve()) return a fulfilled Promise, but they differ in execution timing and behavior details.
execution timing:
new Promise runs synchronouslyPromise.resolve() directly creates a resolved Promisebehavior details:
Promise.resolve will unwrap nested Promises (flattening)
分析:
1、执行时机:
xxxxxxxxxxconsole.log("start");new Promise(resolve => { console.log("new Promise"); resolve();}).then(() => { console.log("then");});console.log("end");输出顺序:
xxxxxxxxxxstartnew Promiseendthen
为什么?
中文解释:
执行顺序:
startnew Promise → 重点:因为里面是同步 → 所以打印 new Promisethen 放入微任务队列endthenEnglish explanation:
startnew Promise → executor runs immediately → new Promisethen callback goes to microtask queueendthen2、行为细节
xxxxxxxxxxPromise.resolve(Promise.resolve(1)) .then(res => console.log(res));输出:1
中文: 下面代码的输出顺序是什么?请解释原因。
English: What is the output order of the following code? Explain why.
xxxxxxxxxxconsole.log("1");setTimeout(() => { console.log("2");}, 0);Promise.resolve().then(() => { console.log("3");});console.log("4");
我的答案是:1,3,4,2。因为我知道setTimeout绝对是延时执行的,即使延迟时间是0。但是最终答案是1,4,3,2。
分析:
xxxxxxxxxxconsole.log("1");👉 同步代码,直接执行
输出:1
xxxxxxxxxxsetTimeout(() => { console.log("2");}, 0);👉 宏任务(macrotask),放入任务队列(task queue) (不会立刻执行)
xxxxxxxxxxPromise.resolve().then(() => { console.log("3");});👉 微任务(microtask),放入微任务队列(microtask queue)
xxxxxxxxxxconsole.log("4");👉 同步代码,直接执行
输出:4
到这里为止(同步执行结束)
当前输出:
xxxxxxxxxx14
接下来执行队列
规则(面试必背):
中文: 每一轮事件循环: 👉 先清空所有微任务 👉 再执行一个宏任务
执行微任务:
输出:
xxxxxxxxxx3
再执行宏任务(setTimeout):
输出:
xxxxxxxxxx2
最终结果
xxxxxxxxxx1432
英文 Explanation:
Synchronous code runs first:
14Then microtasks:
3 (from Promise)Then macrotasks:
2 (from setTimeout)Promise 的 then 属于微任务,会在当前同步代码执行完之后、下一个宏任务之前执行。
Promise callbacks (then) are microtasks, executed after synchronous code but before macrotasks.
Key Rule:
Microtasks always run before macrotasks
中文: 下面代码输出什么?
English: What is the output?
xxxxxxxxxxconsole.log("start");setTimeout(() => { console.log("timeout");}, 0);Promise.resolve() .then(() => { console.log("promise1"); }) .then(() => { console.log("promise2"); });console.log("end");
我的答案:start, end, promise1, promise2, timeout。回答正确。
分析:
1、第一阶段:同步代码
xxxxxxxxxxconsole.log("start");输出:
xxxxxxxxxxstart
xxxxxxxxxxsetTimeout(() => { console.log("timeout");}, 0);👉 宏任务,进入 task queue(暂不执行)
xxxxxxxxxxPromise.resolve() .then(() => { console.log("promise1"); }) .then(() => { console.log("promise2"); });👉 这里很关键:
then → 放入微任务队列then → 不会立即进入队列⚠️ 为什么?
因为第二个 then 要等第一个执行完才会加入队列
xxxxxxxxxxconsole.log("end");输出:
xxxxxxxxxxend
2、第二阶段:执行微任务
当前微任务队列:
xxxxxxxxxxpromise1
执行:
xxxxxxxxxxpromise1
👉 执行完后:
then 才被加入微任务队列继续执行微任务:
xxxxxxxxxxpromise2
3、第三阶段:执行宏任务
xxxxxxxxxxtimeout
核心考点
关键点 1
then 是“链式微任务”
中文: 后一个 then 要等前一个执行完才会进入微任务队列
English:
Each then in a chain is scheduled only after the previous one resolves
关键点 2
微任务队列是“清空执行”的
中文: 一旦开始执行微任务,会一直执行直到队列清空
English: The microtask queue is fully drained before moving to macrotasks
面试加分句:
中文: Promise 的 then 是按链式依赖逐个进入微任务队列,而不是一次性全部进入。
English:
Promise then callbacks are queued sequentially based on resolution, not all at once.
中文: 下面代码输出什么?
English: What is the output?
xxxxxxxxxxPromise.resolve() .then(() => { console.log("A"); return Promise.resolve("B"); }) .then(res => { console.log(res); });Promise.resolve() .then(() => { console.log("C"); });
我的答案:A, B ,C。正确答案是A, C, B。
关键点是:多个 Promise 链之间的微任务是“交替执行”的,不是一个链跑完再跑另一个。
分析:
第一步:同步阶段
所有 Promise 创建完成
第二步:初始化微任务队列
此时微任务队列是:
xxxxxxxxxxthen1(A)thenC
第三步:执行第一个微任务(A)
xxxxxxxxxxconsole.log("A");return Promise.resolve("B");
输出:
xxxxxxxxxxA
⚠️ 关键点来了!!!
这里返回了:
xxxxxxxxxxPromise.resolve("B")
👉 所以:
此时队列变成:
xxxxxxxxxxthenCthen(B)
第四步:执行 thenC
xxxxxxxxxxconsole.log("C");
输出:
xxxxxxxxxxC
第五步:执行 then(B)
xxxxxxxxxxconsole.log("B");
输出:
xxxxxxxxxxB
Key idea:
Microtasks from different Promise chains are interleaved, not executed chain-by-chain.
中文: 当 then 返回一个 Promise 时,后续 then 会被“延迟”,让其他微任务有机会先执行。
English:
When a then returns a Promise, the next then is deferred, allowing other microtasks to run first.
中文: 下面代码输出什么?
English: What is the output?
xxxxxxxxxxconsole.log("start");setTimeout(() => { console.log("timeout1"); Promise.resolve().then(() => { console.log("promise1"); });}, 0);Promise.resolve().then(() => { console.log("promise2"); setTimeout(() => { console.log("timeout2"); }, 0);});console.log("end");
我的答案:start, end, promise2, timeout1, promise1, timeout2。回答正确。
分析,这题涉及:
1、第一轮(主线程 / 主宏任务)
执行同步代码:
xxxxxxxxxxconsole.log("start"); // startsetTimeout() // timeout1 → 宏任务队列Promise.resolve().then() // promise2 → 微任务队列console.log("end"); // end当前输出:
xxxxxxxxxxstartend
当前队列状态:
promise2timeout1清空微任务队列
执行:
xxxxxxxxxxconsole.log("promise2");
输出:
xxxxxxxxxxpromise2
同时执行:
xxxxxxxxxxsetTimeout(() => {console.log("timeout2");}, 0);
👉 加入宏任务队列
此时队列:
timeout1, timeout22、第二轮(执行宏任务 timeout1)
xxxxxxxxxxconsole.log("timeout1");
输出:
xxxxxxxxxxtimeout1
timeout1 内部:
xxxxxxxxxxPromise.resolve().then(() => {console.log("promise1");});
👉 加入微任务队列
清空微任务队列
执行:
xxxxxxxxxxpromise1
3、第三轮(执行宏任务 timeout2)
xxxxxxxxxxtimeout2
Key points:
Microtasks run before macrotasks
Each macrotask is followed by draining all microtasks
Macrotasks are queued in order
中文: 事件循环是按“宏任务 → 清空微任务 → 下一个宏任务”的顺序执行的,微任务可以在执行过程中不断插入。
English: The event loop runs as: macrotask → drain microtasks → next macrotask. Microtasks can be dynamically added during execution.
中文: async/await 和 Promise 的关系是什么?本质上是怎么实现的?
English: What is the relationship between async/await and Promise? How does it work under the hood?
我的回答:async/await 是 promise的语法糖,async/await可以使用同步的方式来写异步代码。怎么实现不知道。
下面看答案:
标准答案(中文)
async/await 本质上是 Promise 的语法糖,它让我们可以用同步的写法来表达异步逻辑。
在底层实现上:
async 函数一定返回一个 Promiseawait 会把后面的表达式转成 Promise(相当于 Promise.resolve())await 会暂停函数执行,把后面的代码放入微任务队列.then)Standard Answer (English)
async/await is essentially syntactic sugar over Promises, allowing asynchronous code to be written in a synchronous style.
Under the hood:
async function always returns a Promiseawait converts the expression into a Promise (like Promise.resolve())await pauses execution and schedules the rest as a microtask.then)
简化的答案:
中文:
async/await 是 Promise 的语法糖,async 函数返回 Promise,await 会把表达式转为 Promise 并暂停执行,把后续代码放入微任务队列,本质上等价于 Promise.then 的链式调用。
English:
Async/await is syntactic sugar over Promises. Async functions return Promises, and await converts expressions into Promises and resumes execution via microtasks, essentially behaving like chained .then calls.
分析:
async/await 写法:
xxxxxxxxxxasync function test() { const res = await Promise.resolve(1); console.log(res);}本质等价于:
xxxxxxxxxxfunction test() { return Promise.resolve(1).then(res => { console.log(res); });}深度解析:
await 做了什么?
xxxxxxxxxxawait xxx
本质可以理解为:
Promise.resolve(xxx).then(result => { // 后面的代码});
关键机制(非常重要)
为什么说它“暂停”?
中文:
English:
.then (microtask)常见追问:
Q:await 后面如果不是 Promise 会怎样?
xxxxxxxxxxawait 123👉 等价于:
xxxxxxxxxxawait Promise.resolve(123)
Q:async 函数 return 什么?
xxxxxxxxxxasync function test() { return 1;}👉 实际返回:
xxxxxxxxxxPromise.resolve(1)
中文:
请你实现一个 Promise.all,并说明它的行为。
English:
Implement Promise.all and explain how it works.
常见题目要求(面试官常说的话):
xxxxxxxxxxPromise.myAll([p1, p2, p3])
需要满足:
分析:
英文版:
简单回答:
“手写 Promise.all 的核心难点有两个:一是用计数器判断所有完成,二是必须按索引存放结果来保证顺序。我会用 Promise.resolve() 来兼容普通值,同时任何一个 reject 都会立刻终止。”
The core idea of Promise.all:
代码:
xxxxxxxxxxPromise.myAll = function(promises) { return new Promise((resolve, reject) => { // 1. Input validation if(!Array.isArray(promises) ) { return reject(new TypeError("Arguments must be an array")) } const len = promises.length; const results = new Array(len); // pre-allocate to keep order let completed = 0; // 这里是重点,如果数组为空,没有加上这句的话,就永远不会resolve if(len === 0) { return resolve([]) } promises.forEach((promise, index) => { // 3、support ordinary values → use Promise.resolve() Promise.resolve(promise).then(value => { results[index] = value; // 4、keep original order by using index completed++; // all succeeded → resolve with results array if(completed === promises.length) { resolve(results) } }).catch(err => { // if anyone fails → immediately reject reject(err) }) }) })}我的一个疑问点:
这个代码里面,为什么第一个判断里面reject,需要使用return,直接reject不行吗?还有 len === 0时的判断,为什么需要return,直接resolve([]) 不行吗?
因为我没有搞清楚resolve和reject的作用,我以为resolve和reject执行之后,后面的代码就不会执行了。不是的,resolve或者reject之后,只是promise的状态确定了,但是里面的代码还是会继续执行下去,如果promises参数都不是数组,那么promises.forEach肯定报错。所以必须使用return来停止代码的执行。
所以说概念很多,需要不断的分辨清楚。
中文
请你实现一个 Promise.race
English
Implement Promise.race
题目要求:
xxxxxxxxxxPromise.myRace([p1, p2, p3])需要满足:
举个例子
xxxxxxxxxxPromise.race([ new Promise(res => setTimeout(() => res(1), 100)), new Promise(res => setTimeout(() => res(2), 50))]).then(console.log)👉 输出:2
分析:
race:赛跑、比赛。那么这个方法的意思就是,谁先完成,就返回谁。
那么参考Promise.all的实现方法,这个其实更简单,因为只需要返回一个settle结果,并且不关心顺序,空数组永远pending(意味着不需要考虑空数组返回什么,因为此时的promise.forEach是无法执行的,外层的promise就会一直pending),直接在then里面resolve并且catch里面reject即可。
中文
Promise.race 的核心是:
English
The core idea of Promise.race:
我的答案:
xxxxxxxxxxPromise.myRace = function(promises) { return new Promise((resolve, reject) => { if(!Array.isArray(promises)) { return reject(new TypeError("Arguments must be an array")) } promises.forEach((promise, _) => { Promise.resolve(promise).then(value => { resolve(value) }).catch(reject) }) })}可以优化的点:
⚠️ 点 1:空数组情况(高频考点)
xxxxxxxxxxPromise.race([])
正确行为:永远 pending(不会 resolve / reject)。我的答案是正确的,但是需要主动进行说明让面试官了解。
中文: 如果传入空数组,race 会返回一个永远 pending 的 Promise。因为promises.forEach不会执行。
English: If an empty array is passed, the returned Promise will remain pending forever.
⚠️ 点 2:可以简化写法(加分)
你现在写的是:
xxxxxxxxxx.then(value => resolve(value)).catch(reject)面试中可以写成更简洁:
x
.then(resolve).catch(reject)为什么可以这样简写?
这种简化在 JavaScript 中被称为 “Point-free” 风格(或者叫函数的一等公民特性)。
能够简化最底层的逻辑是:JavaScript 的函数是一等公民(First-Class Citizens),这意味着函数可以像普通变量一样被传递。
我们可以分层来看为什么它们是等价的:
1. 拆解
.then(value => resolve(value))当我们写
value => resolve(value)时,我们其实是创建了一个中间匿名函数(媒婆)。
它的工作流程是:
- 接收
.then传过来的参数value。- 把这个
value再次作为参数传给resolve函数。2. 为什么可以直接写
.then(resolve)因为
resolve本身就是一个接收一个参数的函数。
.then()期望接收的也是一个函数。- 当你把
resolve直接丢给.then时,.then在成功时会自动调用你给它的这个函数,并把结果作为第一个参数传进去。这就像你雇佣了一个员工(中间函数),他的工作只是把快递(
value)接过来再转交给经理(resolve)。既然如此,你为什么不直接把快递交给经理呢?3. 一个致命的“坑”:什么时候不能这样简化?
在面试中,如果你能主动指出这个简化的边界条件,会显得你的基础极其扎实。
当函数内部依赖
this上下文(Context)时,绝对不能这样简化!错误示例:
假设你有一个对象方法:
xxxxxxxxxxconst userService = {name: 'Database',saveData(data) {// 内部使用了 thisconsole.log(`Saving ${data} to ${this.name}`);}};
这样写可以跑通:
xxxxxxxxxx.then(value => userService.saveData(value))// 没问题,因为 userService 依然在点号(.)左边,this 指向 userService这样写会报错 /
this丢失:xxxxxxxxxx.then(userService.saveData)// 灾难!userService.saveData 被当作普通变量单独传进去了,// 执行时 this 指向了 undefined 或全局对象,导致 `this.name` 报错。面试中的双语解释 (Bilingual Pitch)
"This simplification is possible because in JavaScript, functions are First-Class Citizens. They can be passed around just like values." (这种简化之所以可行,是因为在 JS 中,函数是一等公民。它们可以像值一样被传递。)
"
.then()expects a function definition. Bothvalue => resolve(value)andresolveare functions that take one argument. Passingresolvedirectly avoids creating an unnecessary anonymous wrapper function." (.then()期望接收一个函数定义。这两者都是接收一个参数的函数。直接传递resolve避免了创建不必要的匿名包装函数。)"However, we must be careful with
thiscontext. If the function we are passing relies onthis, like an object method, passing it directly will break the binding, and we would need to use.bind()or keep the arrow function." (但是,我们必须小心this上下文。如果传递的函数依赖this(比如对象的方法),直接传递会导致绑定丢失,此时我们需要使用.bind()或者保留箭头函数。)总结
.then(resolve):简洁、没有创建多余的闭包函数,性能略微好一丢丢。- 只要不涉及
this,大胆地用它去惊艳面试官。
⚠️ 点 3:变量命名(小优化)
xxxxxxxxxx(promises, _)_ 没必要,可以去掉
所以最终答案可以是这样的:
xxxxxxxxxxPromise.myRace = function(promises) { return new Promise((resolve, reject) => { if (!Array.isArray(promises)) { return reject(new TypeError("Arguments must be an array")); } promises.forEach(promise => { Promise.resolve(promise).then(resolve, reject); }); });};
中文解释: 给任意一个 Promise 添加超时时间,如果超过指定时间还未完成,则 reject。
English Explanation: Add a timeout to any promise. If it doesn't settle within the given time, reject with a timeout error.
xxxxxxxxxxfunction withTimeout(promise, ms) { const timeout = new Promise((resolve, reject) => { setTimeout(() => { reject(new Error(`Promise 超时:${ms}ms`)) }, ms) }) return Promise.race([promise, timeout])}// 使用async function fetchData() { const data = await withTimeout(fetch('/api/data'), 5000) return data;}面试回答模板:
“我常用 Promise.race() 来实现超时功能。一个是正常 Promise,另一个是 setTimeout 的 reject Promise,哪个先完成就用哪个。这样可以有效防止接口长时间无响应。”
中文解释: 实现一个函数,限制同时运行的异步任务数量(例如最多同时 3 个请求),常用于防止请求过多导致服务器压力过大,避免瞬间撑爆服务器或浏览器带宽。比如说用户上传图片、文件的时候,可以同时选中很多个文件一起上传,此时使用并发限制函数是很有效的。
English Explanation: Create a function that limits the maximum number of concurrent async tasks (e.g. max 3 requests at the same time).
重点:
await Promise.race 来限制并发的数量,注意这里的await,能够让for...of循环暂停。
思路:
xxxxxxxxxxasync function limitConcurrency(tasks, limit = 3) { const results = []; // 存储所有任务的 Promise const executing = []; // 存储正在执行的任务池 for (const task of tasks) { // 1. 将任务包装成 Promise,并立即开始执行 const p = Promise.resolve().then(() => task()); results.push(p); // 2. 将正在执行的任务加入池子 executing.push(p); // 3. 关键:任务完成后,自动从池子中移除自己 // 无论成功或失败 (finally),都要腾出位置 p.finally(() => { const index = executing.indexOf(p); if (index > -1) executing.splice(index, 1); }); // 4. 如果池子满了,就等待“最快的一个”任务完成,腾出空位 if (executing.length >= limit) { await Promise.race(executing); } } // 5. 当所有任务都发起了,最后等待全部任务的结果 return Promise.all(results);}我的几个问题:
为什么最后的返回结果是Promise.all(results),不应该就返回results吗?因为里面有p.finally 这样的代码啊。
我想象的过程应该是这样的:所有的tasks完成后都会返回数据,所以最后的results里面,应该都是数据而已。
错,即使tasks完成之后返回的是数据,但是task都会被包装为promise,所以返回的结果其实是promise。
并且:
p.finally触发了,并不代表“所有任务”都完成了,它只是代表“当前某一个任务”完成了,而for...of循环在这个时候已经提早结束了。让我们用时间线来拆解为什么
for循环结束时,任务还在继续:
for...of循环只是个“发票机”在这个函数中,
for...of循环的作用是把所有任务安排进队列,它跑得极快(几毫秒内就结束了)。假设你有 10 个任务,限制并发为 3:
① 循环瞬间启动了任务 1、2、3,并将它们推进了
executing池子。② 因为池子满了(长度为 3),代码在
await Promise.race(executing)这里暂停(卡住)了。③ 突然,任务 1 完成了。任务 1 的
p.finally触发,把任务 1 从池子里删掉(池子剩下 2 个)。④ 此时
Promise.race感知到有人完成了,解除暂停。for循环继续往下走,立刻把任务 4 放进池子。
- 当
for循环走出去了,发生了什么?当循环把第 10 个任务也放进池子后,
for循环就彻底结束了。 此时代码来到了最后一行:return ...关键就在这里: 当循环结束、代码走到最后一行的时候,任务 8、9、10 往往才刚刚被放进池子里,它们还在后台努力地执行着呢! 它们的
p.finally此时根本还没有触发。
- 如果这时候直接
return results正如你所说,前面完成的任务(比如任务 1、2、3)已经在历史的某个时刻触发过
p.finally了。但最后这几个任务(8、9、10)此时还是 Promise(正在加载状态)。
- 如果你直接
return results:外部拿到的是[数据1, 数据2, ... Promise8, Promise9, Promise10]。后面几个任务由于还没跑完,被直接当成 Promise 扔给外面了。- 如果你写
return Promise.all(results):它就像一个终点线裁判。它会对整个results数组说:“大家别急,虽然循环已经发完所有跑道了,但我在这守着,必须等最后这几个还没跑完的兄弟(8、9、10)全部冲过终点线,我才把最终所有人的总成绩(纯数据数组)打包送出去。”那最后results里面,可能是p,也可能是p完成后的结果,都放到all里面,没有问题吗?
results数组里其实“永远只有 Promise”这是一个常见的直觉误区。你可能会觉得:任务完成了,它在
results数组里是不是就变成具体的数据了?答案是:不会。
results是一个普通数组。你在第一步把它推入进去的是p(一个 Promise 对象),那么无论过多久、无论这个任务成功还是失败,它在数组里的身份永远是一个 Promise 对象。它的外壳没有变,变的是它内部的状态(从pending变成了fulfilled或rejected)。所以,当你执行
Promise.all(results)时,它拿到的依然是一个纯 Promise 组成的数组,只是这些 Promise 的状态不同:
- 前几个 Promise(比如任务 1、2、3):状态已经是
fulfilled(已成功完成)。- 后几个 Promise(比如任务 8、9、10):状态还是
pending(正在进行中)。
Promise.all是如何处理这些不同状态的?
Promise.all的底层逻辑非常高效和聪明:
- 对于已经完成的 Promise(前几个任务):
Promise.all扫描到它们时,发现状态已经是fulfilled了。它不会等待,而是直接把它们内部保存的最终数据(Resolve 的值)捞出来,放到对应位置的最终结果数组里。
- 对于还没完成的 Promise(最后几个任务):
Promise.all发现它们还在pending。它就会在这些 Promise 上挂上监听器,静静地等待它们完成。当最后一个任务也变成fulfilled时,它就会把所有捞出来的数据打包一起返回。
为什么要使用Promise.resolve().then(() => task()),这种写法。不能使用new Promise这种写法吗?
这两种写法在功能上其实都能达到目的,但使用
Promise.resolve().then(() => task())是一种更稳健、简洁的工业级写法。以下是它们的三大核心区别:
- 自动处理“非 Promise”返回值
在你的
tasks数组里,用户可能不小心传了一个普通函数(不返回 Promise 的函数)。
- 使用
Promise.resolve().then(...):它会自动将task()的返回值包裹成 Promise。即使task只是() => 123,后续的p.finally也能正常工作。- 使用
new Promise:你需要手动在里面写resolve(task())。
- 异常捕获更优雅
这是最关键的原因。
Promise.resolve().then(() => task()):如果task()函数在执行瞬间报错(同步异常),这个错误会被.then自动捕获并转化为一个rejected状态的 Promise。
new Promisexxxxxxxxxx// 如果这样写,如果 task() 执行时报错,由于它在 executor 同步代码里// 你必须手动写 try...catch 才能捕获const p = new Promise((resolve, reject) => {try {resolve(task());} catch(e) {reject(e);}});而
xxxxxxxxxxPromise.resolve().then帮你把这些繁琐的
xxxxxxxxxxtry...catch都省掉了。
- 语义更清晰
new Promise通常用于封装一个原本不是 Promise 的东西(比如setTimeout)。Promise.resolve().then通常用于启动一个已有的异步逻辑,或者将一系列逻辑链条化。在调度器(Limit Concurrency)场景下,我们是在“启动”任务,而不是在“封装”底层 API,所以这种写法更符合语义。如果你非要用
new Promise,它长这样:为了达到同样的效果,你需要写得更复杂:
xxxxxxxxxxconst p = new Promise((resolve, reject) => {// 需要手动立即执行 tasktry {const result = task();// 还需要手动判断 result 是不是 Promise 并处理Promise.resolve(result).then(resolve, reject);} catch (e) {reject(e);}});总结
使用
Promise.resolve().then(() => task())是一种 “懒人写法”,它利用了 Promise 的内部机制自动处理了:
- 同步错误捕获。
- 返回值 Promise 化。
可以这样说,这就是工作造成的,如果工作中会用到promise,我想我会很快掌握的。
掌握好这些面试题,掌握经典的promise用法即可。
你说得非常对!如果你在 Promise 的 .then() 里面又去 new Promise() 并且在里面嵌套写 .then(),那确实会演变成“Promise 地狱”,代码看起来和以前的回调嵌套没区别。
但 Promise 设计的精髓在于“链式调用(Chaining)”,它能把嵌套结构拍平。
这种写法虽然用了 Promise,但思维还是回调函数的思维:
xxxxxxxxxx// ❌ 错误示范:嵌套api.getUser().then(user => { api.getOrders(user.id).then(orders => { api.getOrderDetail(orders[0].id).then(detail => { console.log(detail); }); });});Promise 规定:.then() 方法里的回调函数,如果返回的是一个新的 Promise,那么下一个 .then() 会等待这个 Promise 完成后再执行。
xxxxxxxxxx// ✅ 正确示范:链式调用api.getUser() .then(user => { // 返回一个 Promise return api.getOrders(user.id); }) .then(orders => { // 这里拿到的是上面 getOrders 的结果 return api.getOrderDetail(orders[0].id); }) .then(detail => { // 这里拿到的是 detail console.log(detail); }) .catch(err => { // 整个链条上任何一步出错,都会在这里被捕获 console.error(err); });为了彻底解决这种视觉上的“链条感”,ES2017 引入了 async/await。它本质上还是 Promise,但它让异步代码写起来和同步代码一模一样:
xxxxxxxxxx// 🚀 终极方案:Async/Awaitasync function getInfo() { try { const user = await api.getUser(); const orders = await api.getOrders(user.id); const detail = await api.getOrderDetail(orders[0].id); console.log(detail); } catch (err) { console.error(err); }}总结
return 将逻辑拍平。其实我在很多地方用到了promise,比如说axios, prisma, promise.all(), fetch。使用起来我还是会的,但是我从来没有主动封装过promise相关的工具,那么可以从哪些场景提升自己应用promise的能力呢?
用promise封装一个超时功能。给任意一个 Promise 添加超时时间,如果超过指定时间还未完成,则 reject。
并发限制函数。实现一个函数,限制同时运行的异步任务数量(例如最多同时 3 个请求),常用于防止请求过多导致服务器压力过大。
用promise封装一个请求重试功能。如果接口失败了,自动重试 3 次才彻底报错。
利用闭包记录剩余的重试次数
retries。在catch捕获到错误时,判断retries是否大于 0。如果大于 0,就递减次数并递归调用自身;如果为 0,才把错误reject出去。xxxxxxxxxxfunction retryRequest(requestFn, retries = 3) {return new Promise((resolve, reject) => {const attempt = () => {requestFn().then(resolve) // 成功了直接把结果扔出去.catch((err) => {if (retries > 0) {retries--; // 机会少一次console.log(`请求失败,正在进行重试,剩余次数: ${retries}`);attempt(); // 关键:递归重试} else {reject(err); // 没机会了,彻底报错}});};attempt(); // 启动第一次请求});}使用async...await更加简单,好好想一想。
当你发现你正在写的逻辑里出现了 “监听某个事件(如 onSuccess, onComplete)” 或者是 “在回调函数里套回调函数” 的时候,就是你该主动封装 Promise 的信号。