- Published on
JavaScript 异步编程详解
- Authors
- Name
- 青雲
引言
想象你在一家咖啡店,你下了订单,而后静静地坐着等咖啡制作。这个等待过程,你可以选择读书、刷手机,亦或静静地发呆。而这,便是异步编程在现实生活中的一个缩影:你的订单已经提交给了"后台"(咖啡师),而你可以继续你的"任务"(任何其他活动),不必同步地站在柜台前等待咖啡做好。(耐心和等待一种美德,🐶)
在JavaScript开发中,异步编程是处理耗时任务的高效方式,它让我们的代码在等待某个任务完成时可以去执行其他任务,不必像同步编程一样闲置等待。
异步编程的演进
JavaScript 的异步编程经历了一系列的发展和演进,从早期的回调函数 (Callback) 到现代的Async/Await
。每一个阶段的新特性都解决了之前方法的某些缺点和局限性,使编写异步代码变得更加干净和直观。
回调函数 (Callback)
简介
回调函数是最早的异步编程解决方案。使用回调函数,我们可以在异步操作完成后,调用指定的函数来处理结果。
function fetchData(callback) {
setTimeout(() => {
callback("Data fetched");
}, 1000);
}
function processData(data) {
console.log(data);
}
// 调用示例
fetchData(processData);
缺点
- 回调地狱 (Callback Hell):当多个异步操作需要顺序执行时,嵌套的回调函数会导致代码变得难以阅读和维护。
function step1(callback) {
setTimeout(() => {
console.log("Step 1 complete");
callback();
}, 1000);
}
function step2(callback) {
setTimeout(() => {
console.log("Step 2 complete");
callback();
}, 1000);
}
function step3(callback) {
setTimeout(() => {
console.log("Step 3 complete");
callback();
}, 1000);
}
step1(() => {
step2(() => {
step3(() => {
console.log("All steps complete");
});
});
});
- 错误处理复杂:每个回调函数都需要处理可能发生的错误,使代码变得更加杂乱。
Promise 对象
简介
为了解决回调函数的嵌套问题,ECMAScript 2015(ES6)引入了Promise。它是一种更优雅的异步编程模型,链式调用使得代码更加清晰和可读。Promise 表示一个将来可能完成或失败的事件及其结果值。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
}
function processData(data) {
console.log(data);
}
// 调用示例
fetchData()
.then(processData)
.catch(error => console.error(error));
优点
- 链式调用:Promise 可以通过
.then
和.catch
方法进行链式调用,代码更加线性化、易读。 - 统一的错误处理:Promise 提供了
.catch
方法来进行统一的错误处理,简化了错误处理逻辑。
Generator 函数
简介
Generator 函数是ES6引入的一种异步编程模型。Generator函数可以通过 yield
暂停执行,并通过 next
方法恢复执行,是异步编程的一种更细粒度的控件方式。Generator函数配合Promise
和co
库,可以写出类似同步的异步代码。
function* fetchData() {
const data = yield new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
console.log(data);
}
// 调用示例
const generator = fetchData();
const promise = generator.next().value;
promise.then(data => generator.next(data));
优点
- 暂停和恢复执行:Generator 函数可以在
yield
语法处暂停执行,并在任意时刻恢复执行,使得流程控制更加灵活。 - 配合Promise:Generator函数和Promise结合,能够写出结构化更加清晰的异步代码。
使用 co 库简化 Generator 函数调用
const co = require('co');
function* fetchData() {
const data = yield new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
console.log(data);
}
// 调用示例
co(fetchData);
Async/Await 函数
简介
async/await
是ES2017 (ES8) 引入的语法糖,它建立在Promise之上,使异步代码更加简洁和像同步代码。async
声明函数返回一个Promise,await
关键字暂停函数执行,直到Promise处理完成。
async function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
}
async function processData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error(error);
}
}
// 调用示例
processData();
处理错误
在使用 async/await
时,可以使用 try/catch
语句来捕获和处理错误。这使得错误处理更加直观和集成。
async function fetchData() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject("Error occurred"), 1000);
});
try {
let result = await promise;
console.log(result); // 不会执行,因为 promise 已被 reject
} catch (error) {
console.error(error); // 输出: Error occurred
}
}
fetchData();
优点
- 代码更加简洁:
async/await
让异步代码看起来像同步代码,清晰而易读。 - 统一的错误处理:
try/catch
可以用来处理所有的异步错误,逻辑更加清晰。
总结
JavaScript 异步编程经历了回调函数、Promise、Generator 函数和 Async/Await 函数的演进,每一个阶段的新特性都解决了一些之前方法的缺点。
- 回调函数 (Callback) 是最基础的异步编程方式,但容易导致回调地狱和复杂的错误处理。
- Promise 提供了一种更优雅的方式处理异步操作,通过链式调用和统一的错误处理,提高了代码的可读性。
- Generator 函数 通过
yield
语法实现异步控制,使得流程控制更加灵活,配合co
库,可以写出类似同步的异步代码。 - Async/Await 函数 是现代异步编程的标准,建立在 Promise 之上,使异步代码更加简洁、清晰、易读。
Promise详解
Promise 是一个对象,用于表示一个异步操作的最终完成(或失败)及其结果值。Promise 可以处于以下三种状态之一:
- 待定(Pending):初始状态,既不是成功也不是失败。
- 已完成(Fulfilled):操作成功完成。
- 已拒绝(Rejected):操作失败。
Promise 构造函数接受一个执行器函数作为参数,执行器函数接收两个参数:
resolve
函数:在操作成功时调用,将 Promise 的状态置为“已完成”。reject
函数:在操作失败时调用,将 Promise 的状态置为“已拒绝”。
示例
const myPromise = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve("Operation was successful!");
} else {
reject("Operation failed!");
}
}, 1000);
});
在这个例子中,myPromise
是一个 Promise 对象,模拟了一个异步操作。这个操作等待 1 秒后,根据成功或失败,调用 resolve
或 reject
。
为了处理 Promise 解析后的结果,可以使用 .then()
方法,它接受两个回调函数:一个用于处理成功情况,另一个用于处理失败情况。可以链式调用 .then()
和 .catch()
方法进行连续处理。
myPromise
.then((result) => {
console.log(result); // 输出: Operation was successful!
})
.catch((error) => {
console.error(error); // 输出: Operation failed!
});
Promise 链
一个重要的特性是可以链式调用 .then()
方法。这使得多个异步操作可以串联起来,形成一个异步操作的执行序列。
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
fetchData
.then((result) => {
console.log(result); // 输出: Data fetched
return "Processing data";
})
.then((result) => {
console.log(result); // 输出: Processing data
return "Data processed";
})
.catch((error) => {
console.error(error);
});
fetchData
Promise 被解析并输出 “Data fetched”。- 第一个
.then()
返回新的字符串,传递给下一个.then()
。 - 第二个
.then()
输出 “Processing data” 。
处理多个异步操作
Promise.all()
Promise.all()
接受一个 Promise 数组,当这个数组中的所有 Promise 都已完成时,返回一个 Promise。这个新的 Promise 的结果是一个包含所有已完成 Promise 结果的数组。如果任意一个 Promise 被拒绝,新 Promise 就会立即被拒绝。
const promise1 = new Promise((resolve) => {
setTimeout(() => resolve("Promise 1 resolved"), 1000);
});
const promise2 = new Promise((resolve) => {
setTimeout(() => resolve("Promise 2 resolved"), 2000);
});
Promise.all([promise1, promise2])
.then((results) => {
console.log(results); // 输出: [ 'Promise 1 resolved', 'Promise 2 resolved' ]
})
.catch((error) => console.error(error));
在这个例子中,promise1
和 promise2
是两个异步操作。Promise.all
等待所有操作完成,并返回一个包含两个操作结果的数组。
Promise.race()
Promise.race()
也是接受一个 Promise 数组,但其返回的 Promise 等待数组中的任意一个 Promise 完成或被拒绝,以先发生者为准。
const fastPromise = new Promise((resolve) => {
setTimeout(() => resolve("Fast Promise resolved"), 1000);
});
const slowPromise = new Promise((resolve) => {
setTimeout(() => resolve("Slow Promise resolved"), 3000);
});
Promise.race([fastPromise, slowPromise])
.then((result) => {
console.log(result); // 输出: Fast Promise resolved
})
.catch((error) => console.error(error));
实现简化版的 Promise
步骤 1:设置初始状态
首先,我们定义 Promise 的初始状态为 "pending"。
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((cb) => cb(this.value));
}
};
const reject = (reason) => {
if (this.state === "pending") {
this.state = "rejected";
this.reason = reason;
this.onRejectedCallbacks.forEach((cb) => cb(this.reason));
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
if (this.state === "fulfilled") {
onFulfilled(this.value);
}
if (this.state === "rejected") {
onRejected(this.reason);
}
if (this.state === "pending") {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
}
- 状态管理:我们使用
this.state
、this.value
和this.reason
来表示 Promise 的当前状态、成功的值和失败的原因。 - 回调队列:
this.onFulfilledCallbacks
和this.onRejectedCallbacks
用来存储成功和失败的回调函数。 - resolve 和 reject 函数:通过改变
this.state
来更新 Promise 的状态,并调用对应的回调函数队列。 - 执行器函数:
executor
函数会立即执行,并传入resolve
和reject
函数。在执行过程中,如果抛出错误,则直接调用reject
。
步骤 2:完善链式调用
为了支持链式调用,我们需要在 then 方法中返回一个新的 Promise。
class MyPromise {
// ...前面的代码...
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") {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === "rejected") {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === "pending") {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError("Chaining cycle detected for promise"));
}
let called = false;
if ((x !== null && typeof x === "object") || typeof x === "function") {
try {
const then = x.then;
if (typeof then === "function") {
then.call(x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("Success");
}, 1000);
});
p1.then((data) => {
console.log(data);
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("Another success");
}, 1000);
});
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error(error);
});
- 返回新 Promise:在
then()
方法中,我们返回一个新的 Promise。 - 处理回调:根据当前状态,调用相应的回调函数,并传入
resolvePromise
函数处理返回值。 - resolvePromise 函数:这个函数非常关键,它处理
thenable
(即有then
方法的对象)或返回值,确保 Promise 遵循规范。
完整 Promise 处理
为了使我们的 Promise 更加完整,我们还需要处理:
- .catch() 方法:JavaScript 的 Promise 提供了
.catch()
方法,用于捕获错误。 - 静态方法:例如
Promise.resolve
和Promise.reject
。 - 多态并发:例如
Promise.all
和Promise.race
。
.catch() 方法
class MyPromise {
// ...前面的代码...
catch(onRejected) {
return this.then(null, onRejected);
}
}
Promise.resolve 和 Promise.reject
class MyPromise {
// ...前面的代码...
static resolve(value) {
return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
}
Promise.all 和 Promise.race
class MyPromise {
// ...前面的代码...
static all(promises) {
return new MyPromise((resolve, reject) => {
let resultArray = [];
let count = 0;
promises.forEach((p, index) => {
MyPromise.resolve(p).then((value) => {
resultArray[index] = value;
count += 1;
if (count === promises.length) {
resolve(resultArray);
}
}).catch(reject);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((p) => {
MyPromise.resolve(p).then(resolve).catch(reject);
});
});
}
}
面试实战
1. 什么是异步编程,为什么在 JavaScript 中需要异步编程?
问题描述: 解释什么是异步编程,以及为什么在 JavaScript 中需要使用异步编程。
答案: 异步编程是一种编程范式,它允许在等待一个任务完成时继续执行其他任务,而不是等待该任务完成后再开始下一个任务。比如,异步编程允许在等待一个 HTTP 请求完成时继续执行其他代码。 在 JavaScript 中需要异步编程的原因主要有:
- 单线程环境:JavaScript 是单线程的,这意味着同一时间只能执行一个任务。通过异步编程,长时间运行的任务不会阻塞主线程,从而保持用户界面的响应性。
- IO操作:许多操作(如网络请求、文件读写)是 IO 密集型的,需要等待一定的时间才能完成。异步编程可以高效地处理这些操作。
2. 解释什么是回调函数,并举一个使用回调函数的例子。
问题描述: 解释什么是回调函数,并提供一个使用回调函数的示例。
答案: 回调函数是作为参数传递给另一个函数的函数,这个函数会在未来的某个时间点执行。
示例代码:
function fetchData(callback) {
setTimeout(() => {
callback("Data fetched");
}, 1000);
}
function processData(data) {
console.log(data);
}
// 调用示例
fetchData(processData);
在这个例子中,fetchData
函数接受一个回调函数参数 callback
。在 1 秒延迟之后,callback
函数被调用并输出 "Data fetched"。
3. 解释什么是“回调地狱”(Callback Hell),并展示如何通过 Promise 解决这个问题。
问题描述: 解释什么是回调地狱,并提供一个通过 Promise 解决回调地狱问题的示例。
答案: 回调地狱是指当多个回调函数嵌套时,代码变得难以阅读和维护的状态。它通常出现在需要顺序执行多个异步操作时。
示例代码(回调地狱):
function step1(callback) {
setTimeout(() => {
console.log("Step 1 complete");
callback();
}, 1000);
}
function step2(callback) {
setTimeout(() => {
console.log("Step 2 complete");
callback();
}, 1000);
}
function step3(callback) {
setTimeout(() => {
console.log("Step 3 complete");
callback();
}, 1000);
}
// 回调地狱
step1(() => {
step2(() => {
step3(() => {
console.log("All steps complete");
});
});
});
通过 Promise 解决回调地狱:
function step1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Step 1 complete");
resolve();
}, 1000);
});
}
function step2() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Step 2 complete");
resolve();
}, 1000);
});
}
function step3() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Step 3 complete");
resolve();
}, 1000);
});
}
// 链式调用
step1()
.then(step2)
.then(step3)
.then(() => {
console.log("All steps complete");
});
4. Promise 是什么?请解释 Promise 的基本使用方法。
问题描述: 解释什么是 Promise 并展示 Promise 的基本使用方法。
答案: Promise 是一个对象,用于表示一个异步操作的最终完成(或失败)及其结果值。它有三种状态:
- Pending(待定)
- Fulfilled(已完成)
- Rejected(已拒绝)
示例代码:
const myPromise = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve("Operation was successful!");
} else {
reject("Operation failed!");
}
}, 1000);
});
myPromise
.then((result) => {
console.log(result); // 输出: "Operation was successful!"
})
.catch((error) => {
console.error(error); // 处理错误
});
5. 使用 Promise.all 和 Promise.race,分别解释它们的作用并提供代码示例。
问题描述: 解释 Promise.all
和 Promise.race
的作用,并提供代码示例。
答案: Promise.all
和 Promise.race
是用于处理多个 Promise 的方法。
Promise.all
:等待所有 Promise 完成,并返回一个新的 Promise,该 Promise 的结果是由所有 Promise 结果组成的数组。如果任意一个 Promise 被拒绝,则新的 Promise 被立即拒绝。
示例代码 (Promise.all):
const promise1 = new Promise((resolve) => {
setTimeout(() => resolve("Promise 1 resolved"), 1000);
});
const promise2 = new Promise((resolve) => {
setTimeout(() => resolve("Promise 2 resolved"), 2000);
});
Promise.all([promise1, promise2])
.then((results) => {
console.log(results); // 输出: [ 'Promise 1 resolved', 'Promise 2 resolved' ]
})
.catch((error) => console.error(error));
Promise.race
:等待第一个完成的 Promise,并返回一个新的 Promise,该 Promise 的结果是第一个完成的 Promise 的结果。如果第一个完成的 Promise 被拒绝,则新的 Promise 被立即拒绝。
示例代码 (Promise.race):
const fastPromise = new Promise((resolve) => {
setTimeout(() => resolve("Fast Promise resolved"), 1000);
});
const slowPromise = new Promise((resolve) => {
setTimeout(() => resolve("Slow Promise resolved"), 3000);
});
Promise.race([fastPromise, slowPromise])
.then((result) => {
console.log(result); // 输出: "Fast Promise resolved"
})
.catch((error) => console.error(error));
6. 解释什么是 async/await,并展示其基本用法。
问题描述: 解释 async/await
的作用,并提供基本用法的代码示例。
答案: async/await
是基于 Promise 的语法糖,使得异步代码看起来像同步代码,从而提高代码的可读性和简洁性。async
用于声明一个异步函数,await
用于暂停异步函数的执行,直到 Promise 处理完成。
示例代码:
// 声明异步函数
async function fetchData() {
let promise = new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
// 使用 await 等待 Promise 处理完成
let result = await promise;
console.log(result); // 输出: "Data fetched"
}
// 调用异步函数
fetchData();
7. 如何使用 try/catch 进行 async/await 的错误处理?
问题描述: 解释如何通过 try/catch
来处理 async/await
中的错误。
答案: 通过 try/catch
语句可以捕获和处理 async/await
中的错误。将可能抛出错误的 await
表达式放入 try
块中,如果 Promise 被拒绝,则会抛出异常并执行 catch
块中的代码。
示例代码:
async function fetchData() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error occurred");
}, 1000);
});
try {
let result = await promise;
console.log(result); // 不会执行,因为 Promise 已被拒绝
} catch (error) {
console.error(error); // 输出: "Error occurred"
}
}
// 调用异步函数
fetchData();
8. 如何并行处理多个 async/await 函数?
问题描述: 解释如何通过 Promise.all
并行处理多个 async/await 函数,并提供代码示例。
答案: 可以结合 Promise.all
和 async/await
并行处理多个异步操作。这将显著提高性能,因为多个异步操作将同时进行,而不是依次执行。
示例代码:
async function fetchUser() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: "John Doe" });
}, 1000);
});
}
async function fetchPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ userId, title: "Post 1" },
{ userId, title: "Post 2" },
]);
}, 1000);
});
}
async function displayData() {
try {
const [user, posts] = await Promise.all([fetchUser(), fetchPosts(1)]);
console.log(`User: ${user.name}`);
console.log("Posts:", posts);
} catch (error) {
console.error("Error fetching data", error);
}
}
// 调用示例
displayData();
解释
- 并行执行:
Promise.all([fetchUser(), fetchPosts(1)])
同时执行fetchUser
和fetchPosts
两个异步操作。 - 等待完成:
await
将等待所有 Promise 完成,并返回结果数组[user, posts]
。 - 捕获错误:如果任意一个 Promise 被拒绝,控制将进入
catch
块处理错误。
9. 如何在 async/await 中创建延迟?
问题描述: 解释如何在 async/await
中创建延迟,并提供代码示例。
答案: 可以使用一个返回 Promise 的 setTimeout
函数来实现延迟,然后配合 await
使用。
示例代码:
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function example() {
console.log("Waiting...");
await delay(2000); // 延迟 2 秒
console.log("Done");
}
// 调用示例
example();
10. 如何处理多个并发请求,并在第一个请求完成时取消其余请求?
问题描述: 解释如何使用 Promise.race
处理多个并发请求,并在第一个请求完成时取消其余请求。
答案: 可以使用 Promise.race
来处理多个请求,只要其中一个完成,就立即返回结果,并取消其他请求。
示例代码:
async function fetchWithTimeout(url, timeout) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("Timeout")), timeout);
fetch(url)
.then((response) => response.json())
.then((data) => {
clearTimeout(timer);
resolve(data);
})
.catch((err) => {
clearTimeout(timer);
reject(err);
});
});
}
async function handleRequests() {
const url1 = "https://jsonplaceholder.typicode.com/posts/1";
const url2 = "https://jsonplaceholder.typicode.com/posts/2";
const timeout = 5000; // 超时时间 5 秒
try {
const result = await Promise.race([fetchWithTimeout(url1, timeout), fetchWithTimeout(url2, timeout)]);
console.log("First request completed:", result);
} catch (error) {
console.error("Error:", error);
}
}
// 调用示例
handleRequests();
11. async/await 与 Generator 函数相比有哪些优点?
问题描述: 解释 async/await
与 Generator 函数相比有哪些优点。 答案: async/await
与 Generator 函数都是用于处理异步操作的方案,但 async/await
具有以下优点:
- 语法更简洁:
async/await
更接近同步代码,语法简单、可读性强。 - 内建 Promise 支持:
async/await
内置对 Promise 的支持,不需要外部库(如co
)来运行。 - 更简单的错误处理:
async/await
使用try/catch
来处理错误,与同步代码的错误处理方式一致。
示例代码 (Generator 函数):
const co = require('co');
function* fetchData() {
const data = yield new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
console.log(data);
}
// 调用示例
co(fetchData);
示例代码 (async/await):
async function fetchData() {
const data = await new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched");
}, 1000);
});
console.log(data);
}
// 调用示例
fetchData();
12. 笔试综合题
题目描述
你正在开发一个自动任务调度系统,系统需要执行一系列的异步任务,其中一些任务需要串行执行,另一些任务可以并行执行。每个任务执行都有一定的成功率,任务失败需要进行重试,最多重试三次。任务执行结果需要保存到一个日志中。
具体要求
- 实现一个模拟异步任务的函数
performTask(taskName, successRate, duration)
,该函数返回一个 Promise,模拟任务执行。 - 实现一个
TaskScheduler
类,包含以下方法:addTask(taskName, successRate, duration, parallel)
:添加任务到调度器队列,parallel
表示任务是否并行。run()
:按照顺序执行任务队列,并记录每个任务的执行结果(成功或失败)。
- 实现一个日志系统,记录任务执行的开始时间、结束时间以及结果(成功或失败)。
示例如下
任务模拟函数:
function performTask(taskName, successRate, duration) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() < successRate;
if (success) {
resolve(`${taskName} completed successfully`);
} else {
reject(`${taskName} failed`);
}
}, duration);
});
}
任务调度器类:
class TaskScheduler {
constructor() {
this.tasks = [];
this.logger = [];
}
addTask(taskName, successRate, duration, parallel = false) {
this.tasks.push({ taskName, successRate, duration, parallel, retries: 3 });
}
async runTask(task) {
const { taskName, successRate, duration, retries } = task;
for (let attempt = 1; attempt <= retries; attempt++) {
const startTime = new Date();
try {
const result = await performTask(taskName, successRate, duration);
this.logger.push({
taskName,
startTime,
endTime: new Date(),
attempt,
result: 'success',
});
console.log(result);
return;
} catch (error) {
this.logger.push({
taskName,
startTime,
endTime: new Date(),
attempt,
result: 'failed',
});
console.error(error);
}
}
}
async run() {
const serialTasks = this.tasks.filter(task => !task.parallel);
const parallelTasks = this.tasks.filter(task => task.parallel);
for (let task of serialTasks) {
await this.runTask(task);
}
await Promise.all(parallelTasks.map(task => this.runTask(task)));
this.printLog();
}
printLog() {
console.log('Task Execution Log:');
this.logger.forEach(log => {
console.log(
`Task: ${log.taskName} | Attempt: ${log.attempt} | Start: ${log.startTime.toISOString()} | End: ${log.endTime.toISOString()} | Result: ${log.result}`
);
});
}
}
调用示例
const scheduler = new TaskScheduler();
scheduler.addTask('Task 1', 0.5, 1000, false); // 串行
scheduler.addTask('Task 2', 0.8, 500, true); // 并行
scheduler.addTask('Task 3', 0.7, 1500, true); // 并行
scheduler.addTask('Task 4', 0.9, 1000, false); // 串行
scheduler.run();
预期输出
任务运行并记录日志,每个任务最多重试三次,串行任务按顺序执行,并行任务同时执行。日志系统记录每个任务的开始时间、结束时间和执行结果。
解释
- performTask:模拟异步任务的函数,接受任务名称、成功率和持续时间。返回一个 Promise,根据成功率决定任务是否完成。
- TaskScheduler:任务调度器类,包含添加任务、运行任务和记录日志的方法。
- addTask:添加任务到队列。
- runTask:尝试运行单个任务,最多重试三次。
- run:按顺序执行串行任务,并行执行并行任务,最终打印日志。
- printLog:打印任务执行的详细日志。