首页 > web前端 > js教程 > 正文

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

心靈之曲
发布: 2025-10-18 14:23:14
原创
896人浏览过

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语法正是为此而生。

核心策略:

猫眼课题宝
猫眼课题宝

5分钟定创新选题,3步生成高质量标书!

猫眼课题宝 85
查看详情 猫眼课题宝
  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回调中数据更新不同步问题的详细内容,更多请关注php中文网其它相关文章!

编程速学教程(入门课程)
编程速学教程(入门课程)

编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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