JS实现网络请求的三种方式梳理

作者:函数茶馆时间:2026-10-24 23:50:03浏览:362

本文梳理了JavaScript中实现网络请求的三种主要方式:XMLHttpRequest、Promise以及async/await。通过对比回调函数、链式调用与异步语法糖的写法,解析了如何避免回调地狱,提升代码可读性,并探讨了不同ES版本对浏览器兼容性的影响及Electron环境下的应用建议。

背景

为应对日益增长的测试需求并减少重复劳动,有道智能硬件测试组基于 electron 开发了一系列测试提效工具。

随着工具的快速迭代,代码中嵌套回调函数增多导致工具崩溃风险上升。通过引入 async/await 重构回调逻辑,有效降低了代码量,显著提升了代码的可读性与可维护性。

本文重点梳理基于 XMLHttpRequest、Promise 以及 async/await 三种异步网络请求的实现方式。其中,async/await 允许以接近同步代码的逻辑编写异步程序,从而摆脱繁琐的回调函数嵌套。

前言

在 JavaScript 中,发起单个网络请求相对简单,使用 fetch、axios 或直接调用 XMLHttpRequest 即可满足基本需求。

然而,当需要按顺序执行多个请求以拉取数据时,实现难度显著增加。由于 JavaScript 的网络请求默认是异步的,若需顺序执行,最常见的做法是在回调函数中发起下一个请求,例如以下代码所示:

const requestOptions = {
    method: 'GET',
    redirect: 'follow'
};

fetch('https://xxx.yyy.com/api/zzz/', requestOptions)
    .then(response => response.json())
    .then(data => {
        fetch('https://xxx.yyy.com/api/aaa/'+data.id, requestOptions)
            .then(response => response.json())
            .then(data => {
                console.log(data)
            })
            .catch(error => console.error('error', error));
    })
    .catch(error => console.error('error', error));

假设需要通过两步获取数据:首先从 https://xxx.yyy.com/api/zzz/ 获取包含 data.id 的数据对象,随后利用该 id 发起第二次请求以获取最终目标数据。

使用回调函数实现上述逻辑显得繁琐且易错,尤其在逻辑复杂时,后续修改与维护变得极为困难。

接下来将系统梳理 JavaScript 中几种网络请求的实现方式,旨在帮助开发者摆脱回调地狱,为遇到类似问题的开发者提供参考。

XMLHttpRequest

XMLHttpRequest 是早期前端开发中实现 Ajax 的核心对象。通过该对象创建网络请求的标准流程如下:

// 假设访问http://localhost:3000/user返回json对象{"name":"YouDao"}
const xhr = new XMLHttpRequest();
const url = 'http://localhost:3000/user'

xhr.onreadystatechange = function(){
  if (this.readyState == 4 && this.status == 200){
    const json=JSON.parse(xhr.responseText)
    const name=json.name
    console.log(name)
  }
}
xhr.open('GET',url)
xhr.send()

代码首先实例化一个 XMLHttpRequest 对象 xhr,随后为 xhr.onreadystatechange 绑定 readystatechange 事件回调。接着调用 xhr.open('GET',url) 初始化请求,最后通过 xhr.send() 发送请求。

请求发出后,主程序继续执行而不被阻塞,体现了异步调用的优势。当浏览器接收到响应时,将进入 xhr.onreadystatechange 回调函数。在整个请求生命周期中,xhr.onreadystatechange 会触发四次,readyState 值从 1 递增至 4,仅在 readyState 为 4 时才能获取最终响应数据。

进入第四阶段后,需根据 status 判断响应状态码是否正常,通常状态码为 200 表示请求成功。该示例代码最终将在控制台输出 'YouDao'。

可见,使用 XMLHttpRequest 处理请求时,需为每个请求单独创建对象并绑定事件回调。若涉及多个串联请求,代码结构将变得极其复杂。

Promise

Promise 在 ECMAScript 2015 标准中被引入。当一个事件的执行依赖于另一个事件的返回结果时,使用回调会导致代码复杂度急剧上升。

Promise 对象提供了一种检查操作成功或失败的模式。若操作成功,则返回另一个 Promise,从而使回调的编写更加规范化。

通过 Promise 处理请求的标准流程如下:

const promise = new Promise((resolve,reject)=>{
  let condition = true;
  if (condition) {
    resolve("ok")
  } else {
    reject("failed")
  }
}).then( msg => console.log(msg))
  .catch( err => console.error(err))
  .finally( _ =>console.log("finally"))

上述代码将处理流程串联起来:首先创建 Promise 对象,其构造函数接收一个函数,该函数的第一个参数为成功时执行的 resolve,第二个参数为失败时执行的 reject。

resolve 对应执行成功后 then 中的回调函数,reject 对应执行失败后 catch 中的回调函数。finally 块无论成功或失败都会执行,常用于执行收尾清理工作。

基于 Promise 的网络请求可通过 axios 库或浏览器内置的 fetch API 实现。

使用 axios 库创建请求的流程如下:

import axios from 'axios'
const url = 'http://xxx.yyy.com/'
axios.get(url)
  .then(data => console.log(data))
  .catch(err => console.error(err))

个人更倾向于使用 fetch,它是用于替代 XMLHttpRequest 的浏览器原生 API,无需额外导入库文件。fetch 创建请求的方式与 axios 类似,前文已展示过具体用法,此处不再赘述。

尽管 Promise 简化了回调函数的编写方式,但仍未完全摆脱回调地狱。当多个请求串联时,需在 then 中创建新的 Promise,最终可能导致 'Promise 地狱'。

async/await

async/await 在 ECMAScript 2017 标准中被引入,旨在简化 Promise 的写法,使异步函数调用能够按顺序执行,提升代码的可读性。

以下结合前文示例进行说明:

直接使用 fetch 获取数据的代码如下:

const requestOptions = {
    method: 'GET',
    redirect: 'follow'
};

fetch('https://xxx.yyy.com/api/zzz/', requestOptions)
    .then(response => response.json())
    .then(data => {
        fetch('https://xxx.yyy.com/api/aaa/'+data.id, requestOptions)
            .then(response => response.json())
            .then(data => {
                console.log(data)
            })
            .catch(error => console.error('error', error));
    })
    .catch(error => console.error('error', error));

使用 async/await 改写后的代码为:

async function demo() {
 const requestOptions = {
    method: 'GET',
    redirect: 'follow'
  };

  const response = await fetch('https://xxx.yyy.com/api/zzz/', requestOptions);
  const data = await response.json()
  const response1 = await fetch('https://xxx.yyy.com/api/aaa/'+data.id, requestOptions)
  const data1 = await response1.json()
  console.log(data1)
}

demo().catch(error => console.error('error',error))

改写后的代码逻辑更加清晰,消除了大量嵌套的 then 调用。即使面对一连串的网络请求,也能保持代码的简洁与易读。

当在函数声明前添加 async 关键字时,该函数即为异步函数,调用该函数将返回一个 Promise 对象。

await 关键字用于等待 Promise 对象完成,它仅在异步函数中有效。await 表达式会暂停当前异步函数的执行,直到 Promise 处理完成。

因此,若需使一连串异步函数调用顺序执行,只需将这些函数置于使用 async 修饰的函数中,并在调用前添加 await 关键字,即可确保它们按顺序执行。

结语

通过本文梳理,相信读者已掌握避免回调地狱的方法。需注意,Promise 于 2015 年加入语言规范,而 async/await 于 2017 年加入。若项目需兼容老版本浏览器(如 IE6),则需采用其他方式解决回调地狱问题。

对于 Electron 应用,只要使用近年来的版本即可支持上述特性。Electron 结合了 Chromium 和 Node.js 的优势,特别适合开发跨平台的桌面工具类应用程序。

本文关于 JS 实现网络请求三种方式的梳理到此结束,更多相关 JS 网络请求内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!

作者最新文章