0

0

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

心靈之曲

心靈之曲

发布时间:2025-10-18 14:23:14

|

913人浏览过

|

来源于php中文网

原创

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

node.js的开发实践中,处理异步操作是核心技能之一。然而,由于javascript的单线程非阻塞特性,不正确地管理异步流程常常会导致意想不到的结果,例如本文将探讨的,在`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

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

核心策略:

文心快码
文心快码

文心快码(Comate)是百度推出的一款AI辅助编程工具

下载
  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,我们可以优雅地处理复杂的异步流程,确保数据在正确的时机被收集和处理。这种模式不仅解决了数据更新不同步的问题,也使得代码更加清晰、易于维护。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

541

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

372

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

727

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

470

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

391

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

990

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

653

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

544

2023.09.20

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.1万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 1.9万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号