Javascript中的同步和异步

Quod tempora sit beatae et vitae sed reprehenderit fuga. Aut corrupti nihil error libero veniam. Fuga quo ut ipsa delectus tenetur occaecati aut. Impedit itaque fuga necessitatibus.

JavaScript 中,同步(Synchronous)异步(Asynchronous) 是两种不同的执行方式,它们主要影响代码执行的顺序和程序的响应性。

1. 同步(Synchronous)执行

同步执行意味着任务会按照它们在代码中的顺序逐个执行。每个任务必须等前一个任务完成后才能继续执行,这会导致程序在执行某个任务时阻塞,直到该任务完成。

特点:

  • 阻塞:当前任务执行时,后续任务要等前一个任务完成后才能开始执行。
  • 顺序执行:代码从上到下顺序执行,任务完成后才会继续执行下一个任务。
  • 简单直观:同步代码通常比较简单,容易理解。

示例:同步代码

console.log('Start');

function task1() {
  console.log('Task 1 started');
  for (let i = 0; i < 1e8; i++) {} // 模拟一个耗时操作
  console.log('Task 1 completed');
}

function task2() {
  console.log('Task 2 started');
  for (let i = 0; i < 1e8; i++) {} // 模拟一个耗时操作
  console.log('Task 2 completed');
}

task1();
task2(); // task2 会等 task1 完成后再执行

console.log('End');

输出:

Start
Task 1 started
Task 1 completed
Task 2 started
Task 2 completed
End

在这个例子中,task1 执行完成后,task2 才会开始执行,代码是逐行顺序执行的,task2 要等 task1 完成后才会开始执行。

2. 异步(Asynchronous)执行

异步执行指的是,任务在发起时不会等待其他任务完成,而是继续执行后续代码,任务在后台执行,等到执行完毕后,通过回调函数、Promiseasync/await 机制来处理结果。

特点:

  • 非阻塞:任务在执行时不会阻塞程序的执行,程序可以继续执行后续任务。
  • 并发执行:异步操作通常会在后台执行,其他代码可以继续执行。
  • 需要回调:异步操作常常需要回调函数来处理结果,或者使用 Promise/async/await 等机制来处理。

示例:异步代码(回调函数)

console.log('Start');

function task1(callback) {
  console.log('Task 1 started');
  setTimeout(function() {  // 模拟异步操作
    console.log('Task 1 completed');
    callback();  // 执行回调,通知任务完成
  }, 1000);
}

function task2() {
  console.log('Task 2 started');
  // 模拟同步操作
  console.log('Task 2 completed');
}

task1(function() {
  task2();  // task2 会在 task1 完成后执行
});

console.log('End');

输出:

Start
Task 1 started
End
Task 1 completed
Task 2 started
Task 2 completed

在这个例子中,task1 通过 setTimeout 来模拟一个异步任务,在 task1 执行时,程序不会等待它完成,而是会继续执行 console.log('End')。当 task1 完成后,回调函数会执行 task2

3. 异步与事件循环(Event Loop)

JavaScript 是单线程的,意味着它一次只能做一件事。为了处理 I/O 密集型操作(如网络请求、文件读写等),JavaScript 使用 事件循环(Event Loop) 机制来异步执行代码。

  • 调用栈(Call Stack):执行当前任务的地方,函数调用会压入栈中执行。
  • 任务队列(Task Queue):异步任务(例如 setTimeout, 网络请求的回调等)会被推送到任务队列中,等待主线程空闲时执行。
  • 事件循环(Event Loop):事件循环会检查调用栈是否为空。如果为空,就会从任务队列中取出一个任务执行,直到任务队列为空。

示例:事件循环的演示

console.log('Start');

setTimeout(function() {
  console.log('Inside setTimeout');
}, 0);

console.log('End');

输出:

Start
End
Inside setTimeout

在这个例子中,虽然 setTimeout 设置了 0 毫秒的延迟,JavaScript 仍然会先执行同步代码(console.log('Start')console.log('End'))。setTimeout 的回调会被推到任务队列中,等待调用栈为空时执行,所以 Inside setTimeout 会在同步代码执行完后才打印出来。

4. 异步操作的处理方法

a. 回调函数(Callback)

回调函数是最基本的异步处理方式,它是一个函数,作为参数传递给另一个函数,等异步操作完成时调用。

function fetchData(callback) {
  setTimeout(function() {
    console.log('Data fetched');
    callback();
  }, 1000);
}

fetchData(function() {
  console.log('Callback executed');
});

b. Promise

Promise 是异步编程的一种改进,它代表一个可能完成或失败的异步操作,并允许你在未来的某个时刻处理其结果。Promise 使得异步代码更加可读,并且避免了“回调地狱”(callback hell)。

let fetchData = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('Data fetched');
    resolve('Data');
  }, 1000);
});

fetchData.then((data) => {
  console.log('Received:', data);
}).catch((error) => {
  console.log('Error:', error);
});

c. async/await

async/await 是基于 Promise 的语法糖,使得异步代码看起来像是同步的,从而提高了可读性。async 修饰函数,await 用于等待 Promise 的结果。

async function fetchData() {
  console.log('Fetching data...');
  let data = await new Promise(resolve => {
    setTimeout(() => resolve('Data fetched'), 1000);
  });
  console.log(data);
}

fetchData();

5. 同步与异步的对比

特性同步 (Synchronous)异步 (Asynchronous)
执行顺序按顺序执行,当前任务完成后才会执行下一个任务不按顺序执行,当前任务不会阻塞,后续任务可以立即执行
阻塞阻塞,当前任务完成前不能执行后续任务非阻塞,任务会在后台执行,执行完后通过回调、Promise 或 await 处理结果
性能对于 I/O 密集型操作,性能差,容易阻塞线程高效,适用于处理大量 I/O 操作,不会阻塞主线程
使用场景当任务之间有顺序依赖关系时,适合使用同步当需要同时处理多个任务时,适合使用异步,尤其是 I/O 操作(例如网络请求)

6. 总结

  • 同步执行:任务按顺序逐个完成,适合顺序执行的简单任务。
  • 异步执行:任务可以同时执行,不阻塞主线程,适合 I/O 密集型操作和并发任务。
  • JavaScript的异步机制:通过回调函数、Promiseasync/await 等方式实现,并通过事件循环机制确保异步任务的执行顺序。

异步编程在 JavaScript 中非常重要,尤其是在 Web 开发中,处理异步请求(如 HTTP 请求)和其他 I/O 操作时,异步编程能够显著提高性能和响应速度。

Review after registration

login page

  • Summer 🍊 2个月前
    🍊🍊🍊🍊🍊🍊🍊🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎