异步

回调函数

回调函数是 早期处理异步操作主要依赖于回调函数。当一个函数需要执行一个耗时的任务或者需要等待某些条件满足后再执行,可以使用回调函数来实现异步处理。

传统的回调函数

这是最基础、也是最早期用于处理异步操作的方式。

1
2
3
4
5
6
7
8
9
10
function fetchData(callback) {  
setTimeout(() => {
const data = 'Some data fetched from an asynchronous operation';
callback(data);
}, 1000);
}

fetchData((data)=>{
console.log('Processed data:', data);
});

这种方式可以处理简单的异步操作,但当有多个异步操作需要处理时,回调函数嵌套会导致回调地狱,使代码难以理解和维护。

缺点

  1. 回调地狱:当多个异步操作依赖于彼此或者有顺序关系时,在传统的回调函数中容易导致嵌套过深、代码难以理解和维护。
  2. 错误处理不方便:在传统的回调函数中,错误处理通常需要通过额外的参数进行传递,并且容易造成不必要的混乱。
  3. 同时执行多个异步操作后进行统一处理:使用传统方式实现这个功能也比较复杂。

通过 Promise 对象及其相关方法(如 thencatch),可以更好地解决上述问题

Promise

Promise 是ES6引入的一种用于处理异步操作的对象。它表示一个异步操作的最终完成或失败,并提供了链式调用的方式来处理操作结果。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。通过使用 Promise,可以更好地组织和管理异步代码,避免回调地狱的问题。

特点

  • 状态: Promise 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。初始状态为 pending,可以转变为 fulfilled 或 rejected。
  • 回调注册: 可以通过 then 方法注册回调函数来处理异步操作的结果。then 方法接受两个参数,第一个处理成功时的回调函数,第二个处理失败时的回调函数。
  • 错误处理: 可以通过 catch 方法捕获异步操作中的错误,并进行相应的处理。
  • 链式调用: Promise 的 then 方法返回一个新的 Promise 对象,可以通过链式调用的方式处理多个异步操作,使代码更加清晰和易读。
  • 并行执行: 使用 Promise.all 方法可以同时执行多个异步操作,并在所有操作完成后进行统一处理。
1
2
3
4
5
6
7
8
9
10
11
12
function fetchData() {  
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = 'Some data fetched from an asynchronous operation';
resolve(data);
}, 1000);
});
}

fetchData().then((data) => {
console.log('Processed data:', data);
});

其他静态方法

  1. Promise.resolve(value): 返回一个以给定值解析后的 Promise 对象。如果该值是一个 promise,则该 promise 将返回;如果该值是 thenable(即带有 then 方法的对象),返回的 promise 将“跟随”这个 thenable 的对象,采用它的最终状态;否则以该值为成功状态返回一个新的 Promise 对象。
  2. Promise.reject(reason): 返回一个状态为 rejected 的 Promise 对象,并将给定的 reason 作为失败原因。
  3. Promise.all(iterable): 接收一个可迭代对象,比如数组,里面包含一组 Promise 实例。当所有这些实例都产生结果后,将结果按照顺序放入数组中并传递给回调函数。
  4. Promise.race(iterable): 接收一个可迭代对象,比如数组,里面包含一组 Promise 实例。只要其中有任何一个实例率先改变状态(无论是 fulfilled 还是 rejected),race 方法就会响应改变,并且会把第一个改变状态的实例对应的结果传递给 race 返回的那个新实例。
  5. Promise.allSettled(iterable): 在所有指定的 promise 都已经完成或者已经被拒绝后调用指定回调函数。每个promise在返回时都不需要等待别人完成。
  6. Promise.any(iterable): 在 iterable 参数中只要有其中任意个promise(fulfilled 或 rejected) 状态发生变更,就触发其关联处理程序。
  7. Promise.try(func): 提供了promise化非标准异步模式懂得错误处理方式

Promise 的优点:

  1. 解决了回调地狱问题:Promise 可以通过链式调用的方式,避免了传统回调函数中嵌套过深的问题,使代码更加清晰和易读。
  2. 更好的错误处理:Promise 可以通过 catch 方法捕获和处理异步操作中的错误,使错误处理更加方便和统一。
  3. 支持链式调用:Promise 的 then 方法返回一个新的 Promise 对象,可以通过链式调用的方式处理多个异步操作,使代码更加简洁和易于维护。
  4. 支持并行执行:Promise 提供了 Promise.all 方法,可以同时执行多个异步操作,并在所有操作完成后进行统一处理。

Promise 的缺点:

  1. 无法取消 Promise:一旦创建了一个 Promise,就无法取消它,这可能会导致一些资源的浪费。
  2. 无法捕获同步错误:Promise 只能捕获异步操作中的错误,对于同步代码中的错误无法进行捕获和处理。
  3. 链式调用的可读性:虽然链式调用可以使代码更加简洁,但有时可能会降低代码的可读性,特别是当链式调用过长时。

为了进一步解决 Promise 的一些问题而引入async/await, 它是一种基于 Promise 的语法糖, 虽然 Promise 可以解决回调地狱和错误处理等问题,但是在某些情况下,Promise 的语法仍然比较冗长,可读性不够强,而且需要手动处理错误。

async/await

async/await 是 ES8 引入的异步编程模型。它基于 Promise,并提供了一种更简洁、更易读的方式来编写异步代码。async/await 让异步代码看起来更像是同步代码,使得异步操作的处理更加直观和可控。

1
2
3
4
5
6
7
async function fetchData() {  
return await fetch('https://api.example.com/data');
}

fetchData().then(data => {
console.log(data);
});

async 函数

  • 声明一个 async 函数,相当于创建了一个返回 Promise 的函数。
  • async 函数内部可以使用 await 来等待其他的异步操作完成。

async 关键字用于定义一个异步函数,它可以在函数内部使用 await 关键字来等待一个 Promise 对象的解决(resolve)或拒绝(reject)。异步函数总是返回一个 Promise 对象。

await 表达式

  • 在 async 函数内部使用 await 可以暂停该 async 函数执行,并等待 promise 解决或拒绝后再继续执行。
  • 如果被等待的表达式是一个 promise,则会阻塞后面代码直到该 promise 完成。如果不是 promise,则会将其转换为 resolved 的 promise 并返回其值。

await 关键字只能在异步函数内部使用,它可以暂停异步函数的执行,等待一个 Promise 对象的状态变为 resolved,并返回 Promise 的解决值。在等待期间,await 表达式后面的代码会被暂停执行,直到 Promise 对象的状态变为 resolved。

1
2
let result = await someAsyncOperation();  
console.log(result); // 这行代码会在 someAsyncOperation() 完成后才执行

错误处理

使用 try/catch 块来捕获异步函数中的错误。如果 await 表达式返回的 Promise 对象被拒绝(rejected),则会抛出一个异常,可以通过 catch 块来捕获并处理这个异常。

1
2
3
4
5
6
7
8
9
async function fetchData() {  
try {
let response = await fetch('https://api.example.com/data');
let data = await response.json();
return data;
} catch (error) {
console.error("Error fetching data:", error);
}
}

优点

  1. 代码清晰: async/await 让异步操作看起来像同步操作一样,使得异步代码更易于理解和编写,避免了回调地狱的问题。
  2. 错误处理: 使用 try/catch 结构可以方便地捕获和处理异步操作中的错误,使得错误处理更加直观和简单。
  3. 顺序控制: 可以通过 await 控制异步操作的执行顺序,使得代码逻辑更加清晰,特别适用于需要串行执行多个异步操作的情况。
  4. 结合性能: async/await 基于 Promise,可以很好地结合 Promise 的性能优势,同时提供更直观的语法。

缺点

  1. 仅限异步函数: async/await 只能在 async 函数中使用,如果需要在顶级作用域或普通函数中使用,就需要额外结合 Promise 使用。
  2. 不支持并行: await 会阻塞后续代码的执行,因此无法实现多个异步操作的并行执行,可能影响性能。
  3. 隐式转换: 如果被等待的表达式不

事件监听

事件监听器是一种用于处理异步操作的机制,它允许我们在特定事件发生时执行相应的代码。通过注册事件监听器,可以在异步操作完成时执行相应的回调函数。这种方式常用于处理浏览器中的事件,如点击事件、加载事件等。

异步处理

常见的异步操作包括网络请求、文件读写、定时器等。为了处理这些异步操作,我们可以使用事件监听器来注册回调函数,以便在操作完成时执行相应的代码。

事件监听器

事件监听器是一种机制,用于在特定事件发生时执行相应的代码。在 JavaScript 中,我们可以使用 addEventListener 方法来注册事件监听器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
element.addEventListener(event, callback);
/*
element:要监听事件的元素。
event:要监听的事件类型,如 "click"、"keydown" 等。
callback:事件触发时要执行的回调函数。
*/
// 案例。以下代码注册了一个点击事件监听器,当按钮被点击时,会执行回调函数 handleClick
//当按钮被点击时,会执行回调函数 handleClick。在 handleClick 函数中,我们使用 setTimeout 函数模拟一个异步操作,延迟 2 秒后输出 "Async operation completed!"。
const button = document.querySelector('button');

function handleClick() {
console.log('Button clicked!');

setTimeout(() => {
console.log('Async operation completed!');
}, 2000);
}

button.addEventListener('click', handleClick);

优点

  1. 非阻塞: 事件监听器允许异步操作在后台执行,不会阻塞程序的主线程,提高了程序的性能和响应速度。
  2. 模块化: 使用事件监听器可以将代码按照事件类型和处理逻辑进行模块化,使得代码结构更加清晰。
  3. 松耦合: 通过事件机制,不同模块之间可以相对独立地进行通信和交互,降低了系统各部分之间的耦合度,提高了可维护性。
  4. 灵活性: 可以动态地添加或移除事件监听器,从而增强系统的灵活性,并且方便实现插件化、扩展等功能。

缺点

  1. 回调地狱: 如果多个异步操作有依赖关系,嵌套过多的回调函数可能导致代码难以阅读和维护。这种情况通常被称为“回调地狱”。
  2. 错误处理困难: 在复杂的异步操作链中,错误处理可能变得复杂。需要额外注意避免错误被忽略或混淆。
  3. 顺序控制困难: 在一些场景下需要考虑异步操作执行顺序(如串行执行),这可能需要额外考虑控制逻辑。