上个月面了一家中厂,技术面第二轮,面试官笑眯眯地说:"来,手写一个Promise。" 我脑子嗡了一下------这题我背过,但那是三年前。真要默写,肯定漏一堆边界。我看了他一眼,问:"可以用AI吗?" 他愣了一下,说:"你试试。" 我打开Cursor,对着Composer说:"帮我实现一个符合Promise/A+规范的Promise,包含then、catch、finally。" 三秒后代码生成。他看了两秒,说:"你过了。"
前言
手写Promise,面试经典老题。但2026年了,还有多少人在面试前夜死磕resolve、reject、then的链式调用?我不是说这东西不该学------理解原理很重要。但面试时要你一字不差默写出来,意义在哪?工作中你真的会自己写一个Promise吗?不会,你用原生或者蓝鸟。
这周我面了三家公司,两家允许用AI辅助编码,一家连Stack Overflow都不让开。结果呢?允许AI的那两家我拿到了offer,不让的那家我连二面都没进。不是因为我不会写Promise,而是因为他们考察的还是五年前的能力模型。
今天我就把那场面试的完整过程复盘给你:我是怎么用Cursor生成标准Promise实现的,面试官为什么认可,以及如果面试官不让你用AI,你应该怎么回应。最后附一份可以直接复制的手写Promise代码(带详细注释),你拿去背也行,拿去让AI生成也行。
一、为什么"手写Promise"还是一道高频题?
这题活了快十年了。从ES6诞生到现在,面前端必问。面试官想考察的点其实不是你会不会用Promise,而是:
- 你对异步编程的理解深度(微任务、状态流转、链式调用)
- 你代码的健壮性(边界处理、错误冒泡、值穿透)
- 你是否理解Promise/A+规范(而不是只背了个大概)
但问题是,这些能力真的需要默写几百行代码来验证吗? 一个能讲清楚Promise原理、能说对then的返回值为什么是新的Promise、能解释微任务队列顺序的候选人,即使写代码时借助了AI,他也合格了。
二、面试现场:我是怎么用Cursor"作弊"的
面试官出了题,我没有立刻敲。我说:"我平时主力工具是Cursor,我可以用它辅助编码吗?我可以当场解释每一行代码的作用。" 他犹豫了一下,说:"那你试试,但你要讲清楚。"
我打开Cursor的Composer(快捷键Cmd+K),输入:
实现一个符合Promise/A+规范的Promise类。要求:状态只能从pending变为fulfilled或rejected,且不可逆;then方法返回一个新的Promise;支持链式调用;支持值穿透;支持异步执行;错误捕获。
三秒后,Cursor生成了完整代码。我一行一行给面试官解释:
js
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
} else if (this.state === 'rejected') {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
} else {
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason })
);
}
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
if (promises.length === 0) return resolve(results);
promises.forEach((p, idx) => {
MyPromise.resolve(p).then(
val => {
results[idx] = val;
count++;
if (count === promises.length) resolve(results);
},
reject
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(p => {
MyPromise.resolve(p).then(resolve, reject);
});
});
}
}
// 辅助函数:处理then返回值
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
return;
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let then;
try {
then = x.then;
} catch (err) {
return reject(err);
}
if (typeof then === 'function') {
let called = false;
try {
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} catch (err) {
if (called) return;
reject(err);
}
} else {
resolve(x);
}
} else {
resolve(x);
}
}
面试官听完,问了我两个问题:
- "为什么then里面要用queueMicrotask?" ------ 为了模拟原生Promise的微任务行为。
- "resolvePromise里为什么要判断循环引用?" ------ 防止
const p = new Promise((resolve) => { resolve(p); })这类死循环。
我答出来了。他点了点头,没有继续问。
金句:面试官让你手写Promise,不是要你默写API,而是看你知不知道"为什么要这么写"。
三、面试官为什么认可我用AI?
我把代码解释清楚后,面试官说了一句话:"你能讲明白,说明你懂原理。工具只是手段,不是目的。"
这个时代,会背代码已经不值钱了。AI 30秒就能生成一个标准Promise实现。真正的能力是:
- 你能不能判断AI生成的代码对不对?
- 你能不能优化它(比如去掉冗余逻辑、调整性能)?
- 你能不能把它集成到更大的系统里?
所以,如果你下次面试遇到"手写XXX",大胆问:"我用AI辅助可以吗?我保证每一行都能解释清楚。" 大部分开明的面试官会同意,甚至会更欣赏你------因为你展示了真实的工作方式,而不是应试技巧。
四、如果面试官不让用AI,怎么办?
也简单。你告诉他:我可以手写关键结构,但完整实现需要很多边界处理代码。要不我写核心流程,再口述其他部分?
然后你快速写出骨架:构造函数 + resolve/reject + then的基本逻辑(省略resolvePromise里的细节)。面试官通常不会真让你写全,你展示出理解就够了。
千万不要硬背代码。背错了比不会更尴尬。
五、实测数据:手写Promise到底有多长?
我统计了一下:
- 符合Promise/A+规范的标准实现(含静态方法):约 150-200 行。
- 手写完整代码(不含注释),熟练开发者需要 15-20 分钟。
- 用Cursor生成 + 人工review:3 分钟生成,5 分钟 review。
你在面试中愿意花20分钟默写,还是花8分钟解释原理+让AI生成?
六、注意事项(坑点)
- 如果你用AI生成,一定要能解释每一段的作用。面试官随时会打断问:"为什么这里有queueMicrotask?""为什么then要返回新Promise?" 答不上来,就是减分项。
- AI生成的代码可能不符合你公司的命名风格。没关系,手动改一下变量名。
- 不要完全依赖AI。至少自己手写过一两次,理解核心难点(比如状态流转、微任务队列、值穿透)。
七、写在最后
我最终拿到了那家公司的offer。入职后我问面试官,当时为什么同意我用AI?他说:"因为我们团队每天都在用Cursor。招一个不会用AI的人进来,反而是累赘。"
2026年的前端面试,已经不是在考"你会不会写",而是在考"你会不会用工具写"。手写Promise仍然是一道好题,但考核的重点已经变了。如果你还在靠死记硬背准备面试,可能会越来越吃力。
你在面试中用AI工具被质疑过吗?后来怎么解释的?点个赞让我看到有多少人偷偷用过。