js异步

简介: js异步

JavaScript 中的异步(Asynchronous)是处理耗时操作(如网络请求、文件读写、定时器等)的核心机制,它允许程序在等待这些操作完成时继续执行其他任务,而非阻塞主线程。这对于保证页面流畅性(避免 UI 卡顿)至关重要。

一、为什么需要异步?

JavaScript 是单线程语言(同一时间只能执行一段代码),如果直接执行耗时操作(如等待服务器响应),会导致主线程阻塞,页面出现“假死”(无法点击、滚动等)。
异步机制通过“先注册任务,后续回调执行”的方式,解决了单线程的阻塞问题。

二、异步的三种实现方式

1. 回调函数(Callback)

最早的异步实现方式,通过将后续操作定义为函数,作为参数传递给异步任务,任务完成后自动调用。

示例:定时器回调

console.log('开始');

// 异步任务:1秒后执行回调函数
setTimeout(function callback() {
   
  console.log('定时器完成');
}, 1000);

console.log('结束'); // 先执行,不会等待定时器

输出顺序

开始
结束
定时器完成  // 1秒后执行

问题:多层嵌套时会产生“回调地狱”(Callback Hell),代码可读性差。

// 回调地狱示例(多层嵌套)
fetchData1(function(data1) {
   
  fetchData2(data1, function(data2) {
   
    fetchData3(data2, function(data3) {
   
      // 更多嵌套...
    });
  });
});

2. Promise(ES6 新增)

解决回调地狱问题,通过链式调用(then())组织异步代码,使流程更清晰。

Promise 三种状态

  • pending(进行中):初始状态。
  • fulfilled(已成功):操作完成,调用 resolve() 触发。
  • rejected(已失败):操作出错,调用 reject() 触发。

示例:用 Promise 处理异步请求

// 定义一个返回 Promise 的异步函数
function fetchData() {
   
  return new Promise((resolve, reject) => {
   
    // 模拟网络请求(1秒后返回数据)
    setTimeout(() => {
   
      const success = true;
      if (success) {
   
        resolve({
    name: '鸿蒙', version: '4.0' }); // 成功时返回数据
      } else {
   
        reject(new Error('请求失败')); // 失败时返回错误
      }
    }, 1000);
  });
}

// 调用异步函数(链式调用)
fetchData()
  .then(data => {
   
    console.log('数据:', data);
    return data.version; // 可将结果传递给下一个 then
  })
  .then(version => {
   
    console.log('版本号:', version);
  })
  .catch(error => {
   
    console.error('出错:', error.message); // 捕获所有错误
  })
  .finally(() => {
   
    console.log('无论成功失败都会执行');
  });

优势:扁平化链式调用,避免嵌套;catch() 统一处理错误。

3. async/await(ES7 新增)

基于 Promise 的语法糖,用同步代码的形式编写异步逻辑,可读性更强。

使用规则

  • async 修饰函数:函数返回值自动包装为 Promise。
  • await 修饰 Promise:暂停执行,等待 Promise 完成后再继续。
  • await 只能在 async 函数中使用。

示例:用 async/await 重写上面的代码

// 复用上面定义的 fetchData()(返回 Promise)
async function handleData() {
   
  try {
   
    console.log('开始请求');
    const data = await fetchData(); // 等待 Promise 完成
    console.log('数据:', data);

    const version = data.version;
    console.log('版本号:', version);
  } catch (error) {
   
    console.error('出错:', error.message); // 捕获错误
  } finally {
   
    console.log('处理结束');
  }
}

// 调用异步函数
handleData();

优势:代码结构与同步逻辑一致,最接近自然语言,易于理解和调试。

三、异步执行顺序:事件循环(Event Loop)

JavaScript 异步代码的执行依赖事件循环机制,核心流程:

  1. 同步任务:直接在主线程执行,执行完立即返回。
  2. 异步任务
    • 放入“任务队列”(分为宏任务队列和微任务队列)。
    • 主线程空闲时,优先执行所有微任务,再执行一个宏任务,反复循环。

常见任务类型

  • 宏任务setTimeoutsetIntervalDOM 事件网络请求等。
  • 微任务Promise.then/catch/finallyasync/awaitqueueMicrotask等。

执行顺序示例

console.log('1(同步)');

setTimeout(() => {
   
  console.log('2(宏任务)');
}, 0);

Promise.resolve().then(() => {
   
  console.log('3(微任务)');
});

console.log('4(同步)');

输出顺序

1(同步)
4(同步)
3(微任务)  // 同步任务执行完后,先清空调用微任务
2(宏任务)  // 微任务执行完后,执行一个宏任务

四、实际开发中的异步场景

  1. 网络请求:用 fetchaxios(基于 Promise)获取后端数据。

    async function getUsers() {
         
      const response = await fetch('https://api.example.com/users');
      const users = await response.json(); // 解析 JSON(也是异步)
      console.log(users);
    }
    
  2. 文件操作:浏览器中读取本地文件(如 FileReader)。

    function readFileAsync(file) {
         
      return new Promise((resolve, reject) => {
         
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result); // 成功回调
        reader.onerror = () => reject(reader.error); // 失败回调
        reader.readAsText(file); // 异步读取文件
      });
    }
    
  3. 定时器:延迟执行或定期执行任务(如轮询接口)。

    // 轮询数据(每3秒一次)
    async function pollData() {
         
      while (true) {
         
        await fetchData(); // 等待本次请求完成
        await new Promise(resolve => setTimeout(resolve, 3000)); // 等待3秒
      }
    }
    

总结

  • 异步核心:避免单线程阻塞,提高程序响应速度。
  • 实现方式:从回调函数→Promise→async/await,逐步优化可读性和易用性,推荐优先使用 async/await
  • 关键机制:事件循环决定异步任务的执行顺序,记住“先微任务,后宏任务”。

掌握异步是 JavaScript 进阶的关键,尤其在处理网络请求、定时器等场景时不可或缺。

目录
相关文章
|
4月前
|
前端开发
Promise.all()方法接收的可迭代对象中,如果有一个Promise被拒绝,会发生什么?
Promise.all()方法接收的可迭代对象中,如果有一个Promise被拒绝,会发生什么?
265 108
|
4月前
|
前端开发
Promise.all()方法和Promise.race()方法有什么区别?
Promise.all()方法和Promise.race()方法有什么区别?
396 115
|
4月前
|
前端开发 JavaScript 开发工具
前端开发基础:从零开启网页制作之旅
本文围绕前端开发基础展开,介绍了构建网页的三大核心技术:HTML(定义页面结构,具语义化特性)、CSS(控制视觉样式,含选择器、盒模型等核心概念)、JavaScript(实现动态交互,可操作 DOM),还提及 VS Code、浏览器开发者工具、Git 等必备工具,给出 “先基础后框架” 的学习路径与 MDN Web Docs 等资源,强调实践的重要性,帮助初学者掌握前端基础,为后续进阶奠基。
|
4月前
|
人工智能 弹性计算 自然语言处理
云速搭 AI 助理发布:对话式生成可部署的阿里云架构图
阿里云云速搭 CADT(Cloud Architect Design Tools)推出智能化升级——云小搭,一款基于大模型的 AI 云架构助手,致力于让每一位用户都能“动动嘴”就完成专业级云架构设计。
597 31
|
4月前
|
安全 Java
Java中的Switch表达式:更简洁的多路分支
Java中的Switch表达式:更简洁的多路分支
478 211
|
4月前
|
前端开发 JavaScript
JavaScript中的Async/Await:简化异步编程
JavaScript中的Async/Await:简化异步编程
362 109
|
4月前
|
Java 测试技术
Java浮点类型详解:使用与区别
Java中的浮点类型主要包括float和double,它们在内存占用、精度范围和使用场景上有显著差异。float占用4字节,提供约6-7位有效数字;double占用8字节,提供约15-16位有效数字。float适合内存敏感或精度要求不高的场景,而double精度更高,是Java默认的浮点类型,推荐在大多数情况下使用。两者都存在精度限制,不能用于需要精确计算的金融领域。比较浮点数时应使用误差范围或BigDecimal类。科学计算和工程计算通常使用double,而金融计算应使用BigDecimal。
1723 102