新闻中心

Node.js异步编程实践:解决https.get回调中数据更新不同步问题

2025-10-18
浏览次数:
返回列表

Node.js异步编程实践:解决https.get回调中数据更新不同步问题

在node.js的开发实践中,处理异步操作是核心技能之一。然而,由于j*ascript的单线程非阻塞特性,不正确地管理异步流程常常会导致意想不到的结果,例如本文将探讨的,在`https.get`等网络请求的回调函数中更新的数据,在外部作用域却无法正确获取的问题。这种现象的根源在于对异步执行顺序的误解,即主线程代码不会等待异步操作完成。

理解异步执行的挑战

在Node.js中,像https.get这样的网络请求是典型的异步操作。这意味着当您调用https.get时,它会立即返回并将网络请求放入事件队列中,而不会阻塞主线程。主线程会继续执行后续代码,直到所有同步代码执行完毕,然后才会处理事件队列中的异步回调。

考虑以下原始代码示例:

app.post("/getWeather",(req,res,next)=>{
    const cities=req.body.cities;
    const result={}; // (1) result对象在这里初始化

    cities.map((city)=>{
        https.get(url,(response)=>{
            response.on("data",(data)=>{
                const wdata=JSON.parse(data);
                const temperature=wdata.main.temp;
                result[city]=temperature; // (3) result在这里更新
            });
        }).on("error",(err)=>{
            console.log(err);
            result[city]="NA"; // (4) result在这里更新
        });
    });

    return res.json(result); // (2) result在这里被立即返回
});

在这个示例中,问题出在标记为(2)的return res.json(result);这一行。当cities.map循环开始并触发https.get请求时,这些请求是异步的。主线程会迅速遍历完所有城市并启动所有请求,然后立即执行到(2)处,将result对象返回给前端。然而,此时网络请求的回调函数(即response.on("data")和response.on("error"))可能还没有被触发,result对象仍然是空的{}。只有当网络请求完成后,response.on("data")或response.on("error")才会被调用,更新result对象,但此时响应已经发出。

解决方案:拥抱Promise和Async/Await

为了解决这个问题,我们需要一种机制来“等待”所有异步请求完成,然后再发送响应。J*aScript的Promise和ES8引入的async/await语法正是为此而生。

核心策略:

AI Surge Cloud AI Surge Cloud

低代码数据分析平台,帮助企业快速交付深度数据

AI Surge Cloud 87 查看详情 AI Surge Cloud
  1. 将每个https.get操作封装成一个返回Promise的函数。
  2. 使用Promise.all来等待所有这些Promise都成功解决(resolved)或失败(rejected)。
  3. 在所有Promise完成后,再发送最终的HTTP响应。

代码实现与解析

以下是使用async/await和Promise.all改进后的代码:

const https = require('https'); // 确保引入https模块

app.post("/getWeather", async (req, res, next) => {
  console.log(req.body.cities);

  const cities = req.body.cities;
  const result = {}; // 初始化结果对象
  const promises = []; // 用于存放所有Promise的数组

  // 遍历每个城市,为每个城市创建一个Promise
  cities.forEach((city) => {
    // 假设url是根据city动态生成的,例如:
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY&units=metric`;

    promises.push(
      new Promise((resolve) => {
        https
          .get(url, (response) => {
            let rawData = ''; // 用于累积接收到的数据

            response.on("data", (chunk) => {
              rawData += chunk; // 累积数据
            });

            response.on("end", () => {
              try {
                const wdata = JSON.parse(rawData);
                const temperature = wdata.main.temp;
                result[city] = temperature;
              } catch (e) {
                console.error(`解析 ${city} 数据时出错: ${e.message}`);
                result[city] = "NA"; // 解析失败也标记为NA
              }
              resolve(); // 请求成功或解析失败,标记此Promise完成
            });
          })
          .on("error", (err) => {
            console.log(`请求 ${city} 发生错误: ${err.message}`);
            result[city] = "NA"; // 请求失败
            resolve(); // 错误发生,标记此Promise完成,避免Promise.all阻塞
          });
      })
    );
  });

  // 等待所有Promise完成
  await Promise.all(promises);

  // 所有异步请求完成后,发送包含完整结果的响应
  return res.json(result);
});

代码解析:

  1. app.post("/getWeather", async (req, res, next) => { ... });: 将路由处理函数声明为async,这允许我们在函数内部使用await关键字。
  2. const promises = [];: 创建一个空数组,用于存储每个城市天气请求生成的Promise。
  3. cities.forEach((city) => { ... });: 遍历cities数组。对于每个城市,我们创建一个新的Promise。
  4. new Promise((resolve) => { ... });: 每个https.get调用都被封装在一个Promise中。resolve函数是Promise成功的标志。
  5. response.on("data", (chunk) => { rawData += chunk; });: https.get的response对象会分块发送数据。我们需要累积这些数据直到end事件触发。
  6. response.on("end", () => { ... resolve(); });: 当所有数据接收完毕(end事件触发)时,我们才尝试解析JSON数据并更新result对象。无论解析成功与否,最终都调用resolve(),表示当前城市的请求处理完毕。
  7. response.on("error", (err) => { ... resolve(); });: 如果在请求过程中发生错误,我们记录错误,将该城市的结果设置为"NA",并且同样调用resolve()。这一点至关重要,因为如果错误发生时不调用resolve(),那么Promise.all将永远不会完成,导致服务器挂起。
  8. await Promise.all(promises);: 这是解决方案的核心。Promise.all接收一个Promise数组,并返回一个新的Promise。这个新的Promise会在数组中的所有Promise都解决(或有一个Promise被拒绝)后解决。await关键字会暂停async函数的执行,直到Promise.all返回的Promise解决为止。
  9. return res.json(result);: 一旦await Promise.all(promises);执行完毕,就意味着所有城市的天气请求都已完成并更新了result对象。此时,我们可以安全地将完整的result对象作为响应发送给前端。

注意事项与最佳实践

  • 错误处理的完整性:在Promise.all中,如果任何一个Promise被拒绝(reject),Promise.all会立即拒绝,并返回第一个被拒绝的Promise的错误。在上述示例中,我们通过在on('error')中调用resolve()来确保即使发生错误,单个Promise也能“完成”,从而让Promise.all继续等待其他Promise。如果希望在任何一个请求失败时立即中止整个过程,可以使用reject()而不是resolve(),并对Promise.all的结果进行try...catch处理。
  • on('end')的重要性:确保在response.on('end')中处理数据并调用resolve(),而不是在on('data')中。on('data')可能会被多次触发,而on('end')只会在数据流结束时触发一次,确保您处理的是完整的数据。
  • 并发限制:如果cities数组非常大,同时发起大量的https.get请求可能会对服务器造成压力或超出API的请求限制。在这种情况下,可以考虑使用像p-limit或async库中的async.mapLimit等工具来限制并发请求的数量。
  • 请求超时:长时间的网络请求可能会导致用户等待过久。为https.get请求添加timeout选项,并在超时时处理错误。
  • Promise封装:为了提高代码的可读性和复用性,可以将单个https.get请求封装成一个独立的函数,该函数返回一个Promise。
// 示例:将https.get封装成一个返回Promise的函数
function getWeatherData(city, url) {
  return new Promise((resolve, reject) => {
    https.get(url, (response) => {
      let rawData = '';
      response.on('data', (chunk) => rawData += chunk);
      response.on('end', () => {
        try {
          const wdata = JSON.parse(rawData);
          resolve(wdata.main.temp);
        } catch (e) {
          reject(new Error(`解析 ${city} 数据失败: ${e.message}`));
        }
      });
    }).on('error', (err) => {
      reject(new Error(`请求 ${city} 失败: ${err.message}`));
    });
  });
}

// 在路由中使用
app.post("/getWeather", async (req, res, next) => {
  const cities = req.body.cities;
  const result = {};
  const promises = cities.map(async (city) => {
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY&units=metric`;
    try {
      const temperature = await getWeatherData(city, url);
      result[city] = temperature;
    } catch (error) {
      console.error(error.message);
      result[city] = "NA";
    }
  });

  await Promise.all(promises);
  return res.json(result);
});

总结

掌握Node.js中的异步编程是构建高效、响应式应用的关键。通过理解https.get等操作的异步特性,并有效地利用Promise和async/await,我们可以优雅地处理复杂的异步流程,确保数据在正确的时机被收集和处理。这种模式不仅解决了数据更新不同步的问题,也使得代码更加清晰、易于维护。

以上就是Node.js异步编程实践:解决https.get回调中数据更新不同步问题的详细内容,更多请关注其它相关文章!


# 溧阳网络营销推广  # 创建一个  # 发生错误  # 才会  # 被拒  # 会在  # 我们可以  # 瓮安网络营销推广招聘  # 什么是seo物理结构  # 可以使用  # 上海seo优化流程  # 娄底抖音营销推广的优势  # 喀什地seo  # 兰州网站优化员招聘  # 安顺网站建设费用表  # 刷网站推广链接怎么做  # 外贸seo内容营销策略  # javascript  # 遍历  # 在这里  # 回调  # 作用  # 路由  # ai  # 工具  # 回调函数  # app  # node  # json  # node.js  # 前端  # js  # java 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: Golang如何使用context实现超时取消_Golang context超时取消模式实践  怎么在html里运行vbs脚本_html中运行vbs脚本方法【教程】  千牛数据看板网页版_千牛数据看板网页版访问方法  TikTok网页版直接登录 TikTok网页端官方平台入口  Eclipse怎么运行工程_Eclipse工程运行配置说明  C++如何实现一个智能指针_手动实现C++ shared_ptr的引用计数功能  响应式图片在网页设计中的正确实现方法  如何优雅地解决Livewire文件上传难题?SpatieLivewireFilepond让一切变得简单  c++如何使用TBB库进行任务并行_c++ Intel线程构建模块  c++中的const_cast和reinterpret_cast怎么用_c++四种类型转换  Lar*el 8 多关键词数据库搜索优化实践  在J*a中如何在J*a中使用异常机制记录错误日志_异常日志实践经验  谷歌邮箱网页版官方页面入口 谷歌邮箱网页端快速访问  高德地图沿途添加点失败如何解决 高德多点规划方法  在J*a中如何开发简易博客标签推荐系统_博客标签推荐项目实战解析  现代化 SciPy 一维插值:interp1d 的替代方案与最佳实践  文心一言怎样用插件调度API数据_文心一言用插件调度API数据【API调用】  Golang如何安装Swagger工具_GoSwagger文档生成环境  Excel函数批量查找替换超快方法_Excel用REPLACE和FIND函数秒级替换  192.168.1.1管理中心入口 192.168.1.1路由器网页设置平台  NetBeans Ant项目:自动化将资源文件复制到dist目录的教程  内存疯狂猛猛涨价:主板销量直接腰斩!  漫蛙漫画网页端入口 漫蛙2官方正版漫画站点  windows10怎么关闭系统提示音_windows10彻底静音设置方法  德邦快递查询平台 德邦快递物流信息查询入口  包子漫画官方网站阅读入口-包子漫画在线漫画官网直达链接  《刺客信条4:黑旗》重制版新细节曝光:无缝加载 地图更细致!  Win10如何恢复误删的快捷方式_Win10重建常用软件快捷方式  CSS布局中意外空白:解决padding-top导致的顶部间距问题  解决移动端滚动问题的overflow属性应用指南  Sublime怎么配置Nim语言环境_Sublime Nim代码高亮与补全  Log4j Console Appender性能瓶颈与高并发优化策略  C++如何实现一个装饰器模式_C++设计模式之动态地给对象添加额外职责  快速CSGO开箱网站指南 CSGO开箱平台推荐  Mac怎么使用表情符号_Mac Emoji快捷键面板  腾讯视频怎么举报不良内容_腾讯视频内容举报流程与违规信息处理方法  PPT平滑切换怎么做 PPT炫酷“平滑”切换动画制作教程【必学】  天眼查企业查询官网入口 天眼查官方网页版查询  C++的std::forward_list怎么用_C++ STL中单向链表容器的特点与应用  CSS Flexbox如何实现多行排列_flex-wrap wrap自动换行显示  sublime怎么预览Markdown渲染效果_Markdown Preview插件 for sublime教程  126邮箱账号注册 电脑版登录入口  J*aScript Promise链中如何正确终止后续.then执行并处理错误  电脑安装程序提示“错误1722”怎么办_Windows Installer服务问题解决【教程】  如何有效阻止外部脚本意外修改内联样式的高度属性  内存检查:在VS Code中调试C++时的内存视图  限制HTML日期输入框的日期选择范围  PHP中高效并行检查多链接状态的教程  抖音创作助手登录入口_抖音创作辅助工具官网直达  在J*a中如何使用BigDecimal进行高精度计算_BigDecimal类应用指南 

搜索