JavaScript异步编程完全指南:从基础到实战

更新日期: 2025-10-22 阅读: 183 标签: 异步

JavaScript环境通常是单线程的。这意味着它一次只能处理一个任务。就像银行只有一个服务窗口,前面的人办完业务,后面的人才能开始。


单线程的特点

优点很明显:实现简单,环境单纯,不容易出现复杂的线程安全问题。

缺点也很明显:如果某个任务需要很长时间,后面的所有任务都必须等待。浏览器卡死、页面无响应,往往就是因为某段JavaScript代码运行时间太长。

想象一下,银行窗口前有个人在办理复杂业务,后面排队的人就只能干等着。


同步与异步的区别

为了解决单线程的局限性,JavaScript提供了两种执行模式。

同步模式:任务按顺序执行,前一个完成,后一个才能开始。就像排队买票,必须等前面的人买完,你才能买。

异步模式:任务不需要等待前一个完成。前一个任务开始后,后续任务可以立即执行。等前一个任务完成时,通过回调函数处理结果。这就像在餐厅点餐,点完餐后你不用站在柜台前等待,可以回座位休息,餐好了服务员会送过来。

异步模式在Web开发中极其重要。浏览器中的Ajax请求、服务器端的I/O操作,都需要异步处理,否则用户体验会非常差。

下面介绍JavaScript中主要的异步编程方式。


回调函数

这是最基础的异步处理方式。

// 定时器回调
setTimeout(function() {
  console.log('1秒后执行');
}, 1000);

// Ajax回调
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data));

// 自定义回调
function readFile(callback) {
  // 模拟文件读取
  setTimeout(function() {
    const data = '文件内容';
    callback(data);
  }, 1000);
}

readFile(function(content) {
  console.log('读取到内容:', content);
});

回调函数的优点是简单直接,容易理解。缺点是当回调嵌套过多时,代码会变得难以维护,形成所谓的"回调地狱"。


事件监听

通过监听事件来触发相应的处理函数。

// 监听dom事件
document.getElementById('myButton').addEventListener('click', function() {
  console.log('按钮被点击了');
});

// 自定义事件
class EventEmitter {
  constructor() {
    this.events = {};
  }
  
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  }
  
  emit(eventName, data) {
    const callbacks = this.events[eventName];
    if (callbacks) {
      callbacks.forEach(callback => callback(data));
    }
  }
}

// 使用示例
const emitter = new EventEmitter();
emitter.on('dataReady', function(data) {
  console.log('收到数据:', data);
});

// 触发事件
setTimeout(() => {
  emitter.emit('dataReady', '这是测试数据');
}, 1000);

事件监听的好处是可以解耦代码,一个事件可以绑定多个处理函数。缺点是流程不够清晰,很难追踪事件的传播路径。


Promise

Promise提供了更优雅的异步处理方式。

// 创建Promise
function asyncTask() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = Math.random() > 0.3;
      if (success) {
        resolve('操作成功');
      } else {
        reject('操作失败');
      }
    }, 1000);
  });
}

// 使用Promise
asyncTask()
  .then(result => {
    console.log(result);
    return '下一步操作';
  })
  .then(nextResult => {
    console.log(nextResult);
  })
  .catch(error => {
    console.error('出错:', error);
  })
  .finally(() => {
    console.log('操作完成');
  });

// Promise常用方法
Promise.all([
  fetch('/api/user'),
  fetch('/api/products')
]).then(([user, products]) => {
  console.log('所有请求都完成了');
});

Promise.race([
  fetch('/api/data'),
  new Promise((_, reject) => 
    setTimeout(() => reject('请求超时'), 5000)
  )
]).then(data => {
  console.log(data);
}).catch(error => {
  console.error(error);
});

Promise让异步代码更易读,可以链式调用,避免了回调嵌套。但错误处理相对复杂,需要理解Promise的各种状态。


async/await

async/await让异步代码看起来像同步代码。

// 基本用法
async function fetchData() {
  try {
    console.log('开始获取数据');
    
    const response = await fetch('/api/data');
    const data = await response.json();
    
    console.log('获取到的数据:', data);
    return data;
  } catch (error) {
    console.error('获取数据失败:', error);
    throw error;
  }
}

// 调用async函数
fetchData().then(data => {
  console.log('数据处理完成');
});

// 并行执行多个异步任务
async function parallelTasks() {
  const [user, products, settings] = await Promise.all([
    fetch('/api/user'),
    fetch('/api/products'),
    fetch('/api/settings')
  ]);
  
  return {
    user: await user.json(),
    products: await products.json(),
    settings: await settings.json()
  };
}

// 错误处理
async function safeOperation() {
  try {
    const result = await mightFailOperation();
    return result;
  } catch (error) {
    console.error('操作失败,使用默认值');
    return defaultValue;
  }
}

async/await大大提高了代码的可读性,让错误处理变得更简单。但要注意,await会阻塞后续代码执行,不必要时不要滥用。


Generator函数

Generator可以暂停和恢复函数执行。

function* numberGenerator() {
  console.log('开始执行');
  yield 1;
  console.log('继续执行');
  yield 2;
  console.log('即将结束');
  return 3;
}

// 使用Generator
const gen = numberGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }

// 用Generator处理异步
function* asyncGenerator() {
  const result1 = yield new Promise(resolve => 
    setTimeout(() => resolve('第一步结果'), 1000)
  );
  console.log(result1);
  
  const result2 = yield new Promise(resolve => 
    setTimeout(() => resolve('第二步结果'), 1000)
  );
  console.log(result2);
  
  return '完成';
}

// 执行异步Generator
function runGenerator(genFunc) {
  const gen = genFunc();
  
  function step(nextValue) {
    const result = gen.next(nextValue);
    if (result.done) {
      return Promise.resolve(result.value);
    }
    return Promise.resolve(result.value).then(step);
  }
  
  return step();
}

runGenerator(asyncGenerator);

Generator提供了很强的控制能力,但语法相对复杂,现在大多被async/await替代。


动画帧回调

用于动画和频繁更新的场景。

function animate() {
  let startTime = null;
  const element = document.getElementById('animated');
  let position = 0;

  function step(timestamp) {
    if (!startTime) startTime = timestamp;
    
    const progress = timestamp - startTime;
    position = (progress / 10) % 300;
    
    element.style.transform = `translateX(${position}px)`;
    
    if (progress < 2000) { // 动画运行2秒
      requestAnimationFrame(step);
    }
  }
  
  requestAnimationFrame(step);
}

// 启动动画
animate();

requestAnimationFrame能保证回调函数在浏览器重绘前执行,适合动画场景,比setTimeout更高效。


Web Workers

在后台线程中执行耗时任务。

// 主线程代码
const worker = new Worker('worker.js');

worker.postMessage('开始计算');

worker.onmessage = function(event) {
  console.log('收到Worker结果:', event.data);
  document.getElementById('result').textContent = event.data;
};

worker.onerror = function(error) {
  console.error('Worker错误:', error);
};

// worker.js文件
self.onmessage = function(event) {
  console.log('收到消息:', event.data);
  
  // 模拟耗时计算
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += i;
  }
  
  self.postMessage(`计算结果:${result}`);
};

Web Workers适合CPU密集型任务,不会阻塞主线程。但不能直接操作DOM,需要通过消息传递与主线程通信。


Service Workers

用于离线缓存和网络请求拦截。

// 注册Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(registration => {
      console.log('Service Worker注册成功');
    })
    .catch(error => {
      console.log('注册失败:', error);
    });
}

// sw.js - Service Worker文件
const CACHE_NAME = 'my-cache-v1';
const urlsToCache = [
  '/',
  '/styles.css',
  '/script.js',
  '/images/logo.png'
];

// 安装阶段
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        return cache.addAll(urlsToCache);
      })
  );
});

// 拦截请求
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // 返回缓存或网络请求
        return response || fetch(event.request);
      })
  );
});

// 更新缓存
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== CACHE_NAME) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

Service Workers可以让Web应用离线工作,显著提升用户体验。但要注意,它们只能在HTTPS环境下运行。


实际应用建议

简单场景:使用Promise或async/await,代码清晰易维护。

事件驱动场景:使用事件监听,实现组件间解耦。

耗时计算:使用Web Workers,避免阻塞主线程。

动画效果:使用requestAnimationFrame,保证流畅性。

离线应用:使用Service Workers,提升用户体验。

兼容性考虑:如果需要支持老浏览器,准备好回调函数或Promise的polyfill。

选择异步编程方式时,要考虑代码可维护性、性能需求和浏览器兼容性。现代JavaScript开发中,async/await结合Promise是最常用的组合,既保证了代码可读性,又提供了强大的异步处理能力。

本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/13033

通过alert方法,去理解js中阻塞、局部作用域、同步/异步任务

javascript中alert是Bom中的成员函数,alert对话框是模态的,具有阻塞性质的,不点击是不会执行后续代码的。js的阻塞是指在调用结果返回之前,当前线程会被挂起, 只有在得到结果之后才会继续执行。

如何优化async代码?更好的编写async异步函数

如何优化async代码?更好的编写async函数:使用return Promise.reject()在async函数中抛出异常,让相互之间没有依赖关系的异步函数同时执行,不要在循环的回调中/for、while循环中使用await,用map来代替它

【JS】异步处理机制的几种方式

Javascript语言的执行环境是单线程,异步模式非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。

js异步加载方式有哪些?_详解异步加载js的多种方案

js异步加载又被称为非阻塞加载,浏览器在下载JS的同时,还会进行后续页面处理。那么如何实现js异步加载呢?下面整理了多种实现方案供大家参考。异步加载js方案:Script Dom Element、onload时的异步加载、$(document).ready()、async属性、defer属性、es6模块type=module属性

Nodejs 处理异步(获取异步数据并处理)的方法

回调函数方式:将异步方法如readFile封装到一个自定义函数中,通过将异步方法得到的结果传给自定义方法的回调函数参数。事件驱动方式:使用node events模块,利用其EventEmitter对象

JS常用的几种异步流程控制

JavaScript引擎是基于单线程 (Single-threaded) 事件循环的概念构建的,同一时刻只允许一个代码块在执行,所以需要跟踪即将运行的代码,那些代码被放在一个任务队列 (job queue) 中

前端异步编程之Promise和async的用法

传统的异步解决方案采用回调函数和事件监听的方式,而这里主要记录两种异步编程的新方案:ES6的新语法Promise;ES2017引入的async函数;Generator函数(略)

异步的JavaScript

JS本身是一门单线程的语言,所以在执行一些需要等待的任务(eg.等待服务器响应,等待用户输入等)时就会阻塞其他代码。如果在浏览器中JS线程阻塞了,浏览器可能会失去响应,从而造成不好的用户体验。

js 多个异步的并发控制

请实现如下的函数,可以批量请求数据,所有的URL地址在urls参数中,同时可以通过max参数 控制请求的并发度。当所有的请求结束后,需要执行callback回调。发请求的函数可以直接使用fetch。

解读react的setSate的异步问题

将setState()认为是一次请求而不是一次立即执行更新组件的命令。为了更为可观的性能,React可能会推迟它,稍后会一次性更新这些组件。React不会保证在setState之后,能够立刻拿到改变的结果。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!