ES6 面试题及详细答案 80题 (41-54)-- 异步编程(Promise/Generator/async)
《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux… 。
文章目录
- 一、本文面试题目录
- 41. 什么是Promise?它解决了什么问题?
- 42. Promise有哪些状态?状态之间如何转换?
- 43. 如何创建一个Promise实例?then()方法的作用是什么?
- 创建Promise实例
- then()方法的作用
- 44. Promise的catch()方法与then()的第二个参数有何区别?
- 45. Promise.all()和Promise.race()的区别是什么?请举例说明。
- `Promise.all(iterable)`
- `Promise.race(iterable)`
- 46. Promise.resolve()和Promise.reject()的作用是什么?
- `Promise.resolve(value)`
- `Promise.reject(reason)`
- 47. 如何实现Promise的串行执行?
- 方法1:链式调用(`then()`嵌套)
- 方法2:使用`Array.reduce()`
- 48. Generator函数与普通函数的区别是什么?如何定义?
- 核心区别
- 定义方式
- 49. Generator函数中的yield关键字有什么作用?
- 示例代码
- 50. 如何调用Generator函数并获取其返回值?
- 步骤1:创建迭代器
- 步骤2:通过`next()`方法逐步执行
- 示例代码
- 注意事项
- 51. async/await的作用是什么?它与Promise、Generator有何关系?
- 与Promise、Generator的关系
- 对比示例
- 52. 如何使用async/await处理异步操作?请举例说明。
- 基本用法
- 示例:处理网络请求
- 执行流程
- 53. async函数的返回值是什么类型?
- 示例代码
- 54. 如何在async函数中捕获错误?
- 方式1:`try/catch`语句(推荐)
- 方式2:`await`后链式调用`catch()`
- 注意事项
- 二、80道ES6 面试题目录列表
一、本文面试题目录
41. 什么是Promise?它解决了什么问题?
Promise 是ES6引入的异步编程解决方案,用于表示一个异步操作的最终完成(或失败)及其结果值。它是一个对象,封装了异步操作,并提供了统一的接口来处理操作的成功或失败状态。
解决的问题:
-
回调地狱(Callback Hell):传统异步编程中,多个嵌套的回调函数会导致代码可读性差、维护困难。Promise通过链式调用(
then()
)扁平化代码结构。// 回调地狱示例(传统方式) setTimeout(() => {console.log("第一步");setTimeout(() => {console.log("第二步");setTimeout(() => {console.log("第三步");}, 1000);}, 1000); }, 1000);// Promise链式调用(优化后) new Promise(resolve => {setTimeout(() => {console.log("第一步");resolve();}, 1000); }).then(() => {return new Promise(resolve => {setTimeout(() => {console.log("第二步");resolve();}, 1000);}); }).then(() => {setTimeout(() => {console.log("第三步");}, 1000); });
-
异步操作的状态管理:Promise提供了明确的状态(pending/fulfilled/rejected),避免了回调函数被多次调用或遗漏调用的问题。
-
统一的错误处理:通过
catch()
方法可集中处理链式调用中的所有错误,无需在每个回调中单独处理。
42. Promise有哪些状态?状态之间如何转换?
Promise有三种状态,且状态转换是不可逆的:
- pending(等待中):初始状态,异步操作尚未完成。
- fulfilled(已成功):异步操作完成且成功,状态从
pending
转换为fulfilled
。 - rejected(已失败):异步操作完成但失败,状态从
pending
转换为rejected
。
状态转换规则:
- 只有两种可能的转换:
pending → fulfilled
:通过调用resolve()
触发。pending → rejected
:通过调用reject()
触发。
- 一旦状态转换为
fulfilled
或rejected
,状态将永久固定,不会再变化。
示例:
const promise = new Promise((resolve, reject) => {// 初始状态:pendingsetTimeout(() => {const success = true;if (success) {resolve("操作成功"); // 状态变为fulfilled} else {reject("操作失败"); // 状态变为rejected}}, 1000);
});
43. 如何创建一个Promise实例?then()方法的作用是什么?
创建Promise实例
通过new Promise()
构造函数创建,传入一个执行器函数(executor),该函数接收两个参数:
resolve
:异步操作成功时调用,将Promise状态改为fulfilled
,并传递结果值。reject
:异步操作失败时调用,将Promise状态改为rejected
,并传递错误信息。
示例:
const promise = new Promise((resolve, reject) => {// 模拟异步操作(如网络请求、定时器)const data = { id: 1, name: "测试数据" };const hasError = false;if (hasError) {reject(new Error("获取数据失败")); // 失败时调用reject} else {resolve(data); // 成功时调用resolve}
});
then()方法的作用
then()
是Promise原型上的方法,用于注册状态改变后的回调函数,返回一个新的Promise实例(支持链式调用)。
- 语法:
promise.then(onFulfilled, onRejected)
onFulfilled
:当Promise状态为fulfilled
时执行,接收成功的结果值。onRejected
:可选参数,当Promise状态为rejected
时执行,接收错误信息。
示例:
promise.then((result) => {console.log("成功:", result); // 输出:成功:{ id: 1, name: "测试数据" }return result.name; // 返回值会传递给下一个then()},(error) => {console.log("失败:", error.message);}).then((name) => {console.log("上一步的结果:", name); // 输出:上一步的结果:测试数据});
原理:then()
返回的新Promise状态由回调函数的执行结果决定:
- 若回调返回非Promise值,新Promise状态为
fulfilled
,结果为该值。 - 若回调返回Promise,新Promise状态与该Promise一致。
- 若回调抛出错误,新Promise状态为
rejected
。
44. Promise的catch()方法与then()的第二个参数有何区别?
catch()
和then()
的第二个参数(onRejected
)都可处理Promise的rejected
状态,但存在以下区别:
特性 | catch() 方法 | then(null, onRejected) |
---|---|---|
捕获范围 | 可捕获当前Promise及之前链式调用中所有未处理的错误(包括onFulfilled 中抛出的错误) | 仅捕获当前Promise的rejected 状态,无法捕获onFulfilled 中抛出的错误 |
链式调用中的位置 | 通常放在链式调用的末尾,作为全局错误处理 | 需紧跟在对应Promise后,否则可能遗漏错误 |
可读性 | 语义更清晰,明确表示“捕获错误” | 语义较模糊,不易区分是成功还是失败回调 |
示例:
// 1. catch()可捕获前面所有错误(包括then的onFulfilled中抛出的错误)
new Promise((resolve, reject) => {resolve();
}).then(() => {throw new Error("then中的错误"); // 此处抛出错误}).catch((error) => {console.log("catch捕获:", error.message); // 输出:catch捕获:then中的错误});// 2. then的第二个参数无法捕获前面onFulfilled中抛出的错误
new Promise((resolve, reject) => {resolve();
}).then(() => {throw new Error("then中的错误");},(error) => {// 此处无法捕获上面抛出的错误,因错误发生在onFulfilled中console.log("then的onRejected捕获:", error); }).catch((error) => {console.log("后续catch捕获:", error.message); // 输出:后续catch捕获:then中的错误});
最佳实践:优先使用catch()
处理错误,因其能统一捕获链式调用中所有错误,且代码更易读。
45. Promise.all()和Promise.race()的区别是什么?请举例说明。
Promise.all()
和Promise.race()
都是Promise的静态方法,用于处理多个Promise实例,但行为不同:
Promise.all(iterable)
- 作用:接收一个可迭代对象(如数组),等待所有Promise都变为
fulfilled
后,返回一个fulfilled
状态的新Promise,结果为所有Promise的结果组成的数组。 - 失败处理:若任一Promise变为
rejected
,则立即返回rejected
状态的新Promise,结果为该错误信息。
示例:
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
const promise3 = Promise.resolve(3);// 所有成功时
Promise.all([promise1, promise2, promise3]).then((results) => {console.log("all结果:", results); // 输出:all结果:[1, 2, 3](等待所有完成)}).catch((error) => {console.log("all错误:", error);});// 有一个失败时
const promise4 = Promise.reject(new Error("出错了"));
Promise.all([promise1, promise4, promise2]).catch((error) => {console.log("all错误:", error.message); // 输出:all错误:出错了(立即失败)});
Promise.race(iterable)
- 作用:接收一个可迭代对象,返回一个新Promise,其状态与第一个完成(变为
fulfilled
或rejected
)的Promise一致,结果为该Promise的结果。 - 特点:“竞速”机制,只关注第一个完成的Promise。
示例:
const fastPromise = new Promise((resolve) => setTimeout(() => resolve("快的成功"), 100));
const slowPromise = new Promise((resolve) => setTimeout(() => resolve("慢的成功"), 1000));
const errorPromise = new Promise((_, reject) => setTimeout(() => reject("出错了"), 500));// 第一个成功的Promise决定结果
Promise.race([fastPromise, slowPromise]).then((result) => {console.log("race结果:", result); // 输出:race结果:快的成功(100ms后)});// 第一个失败的Promise决定结果
Promise.race([errorPromise, slowPromise]).catch((error) => {console.log("race错误:", error); // 输出:race错误:出错了(500ms后)});
总结:
Promise.all()
:等待所有完成(“全成则成,一败则败”),适合依赖多个异步操作结果的场景。Promise.race()
:等待第一个完成(“谁先完成听谁的”),适合设置超时时间等场景(如请求超时处理)。
46. Promise.resolve()和Promise.reject()的作用是什么?
Promise.resolve()
和Promise.reject()
是Promise的静态方法,用于快速创建状态为fulfilled
或rejected
的Promise实例。
Promise.resolve(value)
- 作用:返回一个状态为
fulfilled
的Promise实例,结果为value
。 - 特殊处理:
- 若
value
是Promise,直接返回该Promise(状态和结果一致)。 - 若
value
是具有then()
方法的对象(thenable),会将其转为Promise并执行then()
。 - 其他情况,返回以
value
为结果的fulfilled
Promise。
- 若
示例:
// 1. 普通值
const p1 = Promise.resolve("成功");
p1.then((v) => console.log(v)); // 输出:成功// 2. 传入Promise
const p2 = Promise.resolve(Promise.reject("失败"));
p2.catch((e) => console.log(e)); // 输出:失败// 3. 传入thenable对象
const thenable = {then(resolve) {resolve("thenable的结果");}
};
const p3 = Promise.resolve(thenable);
p3.then((v) => console.log(v)); // 输出:thenable的结果
Promise.reject(reason)
- 作用:返回一个状态为
rejected
的Promise实例,结果为reason
(通常是错误对象)。 - 特点:无论
reason
是什么类型,始终返回rejected
状态的Promise,且reason
直接作为错误结果。
示例:
// 1. 传入错误对象
const p4 = Promise.reject(new Error("故意失败"));
p4.catch((e) => console.log(e.message)); // 输出:故意失败// 2. 传入普通值
const p5 = Promise.reject("简单错误");
p5.catch((e) => console.log(e)); // 输出:简单错误// 3. 传入Promise(仍返回rejected状态,结果为该Promise)
const p6 = Promise.reject(Promise.resolve("成功"));
p6.catch((e) => console.log(e)); // 输出:Promise { <fulfilled>: '成功' }
应用场景:
- 快速将同步值转为Promise,统一异步接口。
- 简化Promise创建代码(如
Promise.resolve()
比new Promise(resolve => resolve(value))
更简洁)。
47. 如何实现Promise的串行执行?
Promise的串行执行指多个异步操作按顺序执行(前一个完成后再执行下一个),可通过以下方式实现:
方法1:链式调用(then()
嵌套)
利用then()
返回新Promise的特性,将下一个异步操作放在前一个then()
的回调中。
// 定义3个异步函数
const asyncTask1 = () => {return new Promise(resolve => {setTimeout(() => {console.log("任务1完成");resolve(1);}, 1000);});
};const asyncTask2 = (prevResult) => {return new Promise(resolve => {setTimeout(() => {console.log("任务2完成(依赖前一个结果:", prevResult, ")");resolve(2);}, 1000);});
};const asyncTask3 = (prevResult) => {return new Promise(resolve => {setTimeout(() => {console.log("任务3完成(依赖前一个结果:", prevResult, ")");resolve(3);}, 1000);});
};// 串行执行
asyncTask1().then((result1) => asyncTask2(result1)).then((result2) => asyncTask3(result2)).then((finalResult) => {console.log("所有任务完成,最终结果:", finalResult);});
// 输出顺序:任务1完成 → 任务2完成 → 任务3完成 → 所有任务完成...
方法2:使用Array.reduce()
对于动态数量的异步任务(如数组中的任务列表),可通过reduce()
实现串行。
// 任务列表(数组形式)
const tasks = [asyncTask1, asyncTask2, asyncTask3];// 使用reduce串行执行
tasks.reduce((prevPromise, currentTask) => {return prevPromise.then((prevResult) => currentTask(prevResult));
}, Promise.resolve()) // 初始值为resolved状态的Promise.then((finalResult) => {console.log("所有任务完成,最终结果:", finalResult);});
原理:reduce()
将前一个Promise的结果传递给下一个任务,通过then()
确保顺序执行。每个任务的返回值会作为下一个任务的输入,实现依赖传递。
48. Generator函数与普通函数的区别是什么?如何定义?
Generator函数是ES6引入的一种特殊函数,它可以暂停执行和恢复执行,与普通函数相比有显著区别:
核心区别
特性 | Generator函数 | 普通函数 |
---|---|---|
定义方式 | 函数名前加* (function* ) | 无* (function ) |
执行机制 | 可暂停(通过yield )和恢复(通过next() ) | 一旦调用,连续执行到结束,不可暂停 |
返回值 | 返回迭代器对象(Iterator) | 返回函数执行结果(无返回值则为undefined ) |
关键语法 | 内部可使用yield 关键字 | 无yield 关键字 |
构造函数能力 | 不能作为构造函数(new 调用会报错) | 可作为构造函数(new 调用生成实例) |
this绑定 | 迭代器对象不继承Generator函数的prototype | 实例继承构造函数的prototype |
定义方式
通过function*
声明,函数体中可使用yield
关键字暂停执行:
// 定义Generator函数
function* myGenerator() {yield "第一步"; // 暂停点1yield "第二步"; // 暂停点2return "完成"; // 结束点
}
调用Generator函数时,不会立即执行函数体,而是返回一个迭代器对象:
const generator = myGenerator(); // 返回迭代器,函数体未执行
49. Generator函数中的yield关键字有什么作用?
yield
是Generator函数特有的关键字,主要作用是暂停函数执行并返回值,类似“断点”,具体功能如下:
-
暂停执行:
当Generator函数执行到yield
时,会暂停当前执行,将yield
后的表达式值作为返回结果。 -
返回值:
每次暂停时,通过迭代器的next()
方法返回一个对象{ value: 结果, done: 布尔值 }
,其中value
是yield
后的表达式值,done
表示是否执行完毕(false
为暂停,true
为结束)。 -
接收输入:
后续调用next(arg)
时,arg
会作为上一个yield
的返回值,实现向暂停处传递数据。 -
迭代委托:
通过yield*
可将执行权委托给另一个Generator函数(或可迭代对象),简化嵌套迭代。
示例代码
function* generatorWithYield() {console.log("开始执行");const input1 = yield "请输入第一个值"; // 暂停并返回提示,后续next()的参数会赋给input1console.log("收到第一个值:", input1);const input2 = yield "请输入第二个值";console.log("收到第二个值:", input2);return input1 + input2;
}// 调用Generator函数
const gen = generatorWithYield();// 第一次调用next():执行到第一个yield,返回{ value: "请输入第一个值", done: false }
console.log(gen.next());
// 输出:
// 开始执行
// { value: '请输入第一个值', done: false }// 第二次调用next(10):将10作为上一个yield的返回值(input1=10),执行到第二个yield
console.log(gen.next(10));
// 输出:
// 收到第一个值:10
// { value: '请输入第二个值', done: false }// 第三次调用next(20):将20作为上一个yield的返回值(input2=20),执行到return
console.log(gen.next(20));
// 输出:
// 收到第二个值:20
// { value: 30, done: true }
50. 如何调用Generator函数并获取其返回值?
调用Generator函数的过程分为创建迭代器和逐步执行两步,最终通过迭代器获取返回值:
步骤1:创建迭代器
调用Generator函数时,不会立即执行函数体,而是返回一个迭代器对象:
function* myGenerator() {yield 1;yield 2;return 3; // 最终返回值
}const iterator = myGenerator(); // 创建迭代器(函数体未执行)
步骤2:通过next()
方法逐步执行
迭代器的next()
方法用于恢复执行,每次调用会:
- 执行到下一个
yield
或return
- 返回对象
{ value: 当前结果, done: 是否完成 }
当done
为true
时,value
即为Generator函数的返回值。
示例代码
// 第一次调用next():执行到第一个yield,返回yield后的值
console.log(iterator.next()); // { value: 1, done: false }// 第二次调用next():执行到第二个yield
console.log(iterator.next()); // { value: 2, done: false }// 第三次调用next():执行到return,返回最终结果
console.log(iterator.next()); // { value: 3, done: true } → 此处获取返回值3
注意事项
-
若函数没有
return
,最终value
为undefined
:function* emptyReturn() { yield 1; } console.log(emptyReturn().next().next()); // { value: undefined, done: true }
-
可通过
for...of
循环遍历yield
的值,但无法获取return
的返回值(循环会在done
为true
时终止):for (const value of myGenerator()) {console.log(value); // 输出1、2(遗漏return的3) }
51. async/await的作用是什么?它与Promise、Generator有何关系?
async/await
是ES2017引入的异步编程语法糖,作用是用同步代码的形式编写异步操作,简化Promise的链式调用。
与Promise、Generator的关系
-
基于Promise:
async
函数的返回值是Promise,await
后面必须跟Promise(非Promise值会被自动包装为Promise.resolve(值)
)。 -
替代Generator+co模块:
async/await
的功能与“Generator函数+自动执行器(如co模块)”类似,但语法更简洁、语义更明确:async
对应function*
await
对应yield
- 内置自动执行逻辑(无需手动调用
next()
)
对比示例
// 1. Promise链式调用
fetchData().then(data => processData(data)).then(result => saveResult(result)).catch(error => handleError(error));// 2. Generator+co(需外部库支持自动执行)
const co = require('co');
co(function* () {try {const data = yield fetchData();const result = yield processData(data);yield saveResult(result);} catch (error) {handleError(error);}
});// 3. async/await(内置自动执行,最简洁)
async function handleData() {try {const data = await fetchData();const result = await processData(data);await saveResult(result);} catch (error) {handleError(error);}
}
52. 如何使用async/await处理异步操作?请举例说明。
async/await
通过async
声明异步函数,await
等待Promise完成,用同步代码结构处理异步逻辑,步骤如下:
基本用法
- 用
async
声明函数(自动返回Promise)。 - 在函数内部用
await
等待异步操作(需是Promise)。 - 用
try/catch
捕获错误(替代Promise的catch()
)。
示例:处理网络请求
// 模拟异步网络请求
function fetchUser(id) {return new Promise((resolve, reject) => {setTimeout(() => {if (id > 0) {resolve({ id, name: `User${id}` }); // 成功返回用户数据} else {reject(new Error("无效的用户ID")); // 失败返回错误}}, 1000);});
}function fetchPosts(userId) {return new Promise((resolve) => {setTimeout(() => {resolve([`Post by User${userId} #1`, `Post by User${userId} #2`]);}, 1000);});
}// 使用async/await处理异步流程
async function getUserPosts(userId) {try {// 等待获取用户信息(同步写法,实际异步执行)const user = await fetchUser(userId);console.log("获取用户:", user);// 等待获取用户文章(依赖上一步结果)const posts = await fetchPosts(user.id);console.log("获取文章:", posts);return { user, posts }; // 返回结果会被包装为Promise} catch (error) {// 捕获所有异步操作的错误console.error("出错了:", error.message);throw error; // 可向上抛出错误}
}// 调用异步函数
getUserPosts(1).then(result => console.log("最终结果:", result)).catch(error => console.log("外部捕获错误:", error.message));
执行流程
- 调用
getUserPosts(1)
,进入函数。 await fetchUser(1)
:等待1秒,获取用户数据。await fetchPosts(1)
:再等待1秒,获取文章列表。- 返回结果,外层
then()
接收。 - 若任何一步出错(如
getUserPosts(0)
),catch()
捕获错误。
53. async函数的返回值是什么类型?
async
函数的返回值始终是Promise对象,无论函数体内返回什么值,都会被自动包装为Promise:
-
返回非Promise值:
结果会被包装为Promise.resolve(返回值)
。 -
返回Promise:
直接返回该Promise(状态和结果保持一致)。 -
抛出错误:
相当于返回Promise.reject(错误对象)
。
示例代码
// 1. 返回普通值 → 包装为resolved Promise
async function returnString() {return "hello";
}
console.log(returnString()); // Promise { 'hello' }
returnString().then(v => console.log(v)); // 输出:hello// 2. 返回Promise → 直接返回该Promise
async function returnPromise() {return Promise.resolve(123);
}
returnPromise().then(v => console.log(v)); // 输出:123// 3. 抛出错误 → 包装为rejected Promise
async function throwError() {throw new Error("出错了");
}
throwError().catch(e => console.log(e.message)); // 输出:出错了
54. 如何在async函数中捕获错误?
在async
函数中捕获错误主要有两种方式,可根据场景选择:
方式1:try/catch
语句(推荐)
在async
函数内部用try
包裹await
操作,catch
捕获所有异步错误(包括await
的Promise失败和同步错误)。
async function handleErrors() {try {// 捕获await的Promise错误const data = await Promise.reject(new Error("异步操作失败"));// 若上面出错,下面代码不会执行console.log("数据:", data);} catch (error) {console.error("捕获错误:", error.message); // 输出:捕获错误:异步操作失败}
}handleErrors();
方式2:await
后链式调用catch()
对单个await
的Promise单独添加catch()
,仅捕获该操作的错误,不影响后续代码。
async function handleSingleError() {// 单独捕获当前await的错误const data = await Promise.reject(new Error("单个操作失败")).catch(error => {console.error("单个错误处理:", error.message); // 输出:单个错误处理:单个操作失败return null; // 返回默认值,避免后续代码出错});// 错误处理后,代码可继续执行console.log("继续执行,data =", data); // 输出:继续执行,data = null
}handleSingleError();
注意事项
try/catch
可捕获整个代码块中的所有错误(包括多个await
和同步错误),适合统一处理。- 单个
catch()
适合对不同异步操作进行差异化错误处理。 - 若未捕获错误,错误会向外层传递,可在调用
async
函数时用.catch()
捕获:async function unhandledError() {await Promise.reject(new Error("未处理的错误")); }unhandledError().catch(e => console.log("外部捕获:", e.message)); // 有效
二、80道ES6 面试题目录列表
文章序号 | ES6 80道 |
---|---|
1 | ES6面试题及详细答案80道(01-05) |
2 | ES6面试题及详细答案80道(06-12) |
3 | ES6面试题及详细答案80道(13-21) |
4 | ES6面试题及详细答案80道(22-32) |
5 | ES6面试题及详细答案80道(33-40) |
6 | ES6面试题及详细答案80道(41-54) |
7 | ES6面试题及详细答案80道(55-61) |
8 | ES6面试题及详细答案80道(62-80) |