【问题标题】:Node.js loop on API callsAPI 调用上的 Node.js 循环
【发布时间】:2019-11-01 10:12:34
【问题描述】:

我正在尝试使用不同参数进行少量 API 调用。获取数据并将其按天、城市最高温度、城市最低温度和有雨的城市转换为 CSV 文件。

API 示例: https://samples.openweathermap.org/data/2.5/forecast?q=M%C3%BCnchen,DE&appid=b6907d289e10d714a6e88b30761fae22

我有以下带有城市和 api 密钥的对象:

const cities = {
    0: ['Jerusalem', 'il'],
    1: ['New York', 'us'],
    2: ['Dubai', 'ae'],
    3: ['Lisbon', 'pt'],
    4: ['Oslo', 'no'],
    5: ['Paris', 'fr'],
    6: ['Berlin', 'de'],
    7: ['Athens', 'gr'],
    8: ['Seoul', 'kr'],
    9: ['Singapore', 'sgp'],
}

const apiKey = "[retracted]";

这是我想要动态迭代的 API 调用,目前我只在第一个对象参数上运行它,并最终将信息推送到天气,以便我可以操作数据以按天(前 5 天)对其进行排序,然后显示温度最高的城市、温度最低的城市和所有下雨的城市:

request(`http://api.openweathermap.org/data/2.5/forecast?q=${cities[1][0]},${cities[1][1]}&mode=json&appid=${apiKey}`, (error, response, body) => {
    let data = JSON.parse(body);    
    let weather = {
        0: [day, highTemp, lowTemp, rain],
        1: [day, highTemp, lowTemp, rain],
        2: [day, highTemp, lowTemp, rain],
        3: [day, highTemp, lowTemp, rain],
        4: [day, highTemp, lowTemp, rain],
    }
    // day 1
    console.log(data['city']['name']);
    console.log(data['list'][0].dt_txt);
    console.log(data['list'][0].main['temp']);

    // day 2
    console.log(data['city']['name']);
    console.log(data['list'][8].dt_txt);
    console.log(data['list'][8].main['temp']);
    // day 3
    console.log(data['city']['name']);
    console.log(data['list'][16].dt_txt);
    console.log(data['list'][16].main['temp']);
    // day 4
    console.log(data['city']['name']);
    console.log(data['list'][24].dt_txt);
    console.log(data['list'][24].main['temp']);
    // day 5
    console.log(data['city']['name']);
    console.log(data['list'][32].dt_txt);
    console.log(data['list'][32].main['temp']);

});

我尝试在对象中使用带有键的 for 循环,但不幸的是它不显示数据,错误原因未定义。

【问题讨论】:

  • 您是在响应中还是在正文中获取数据?
  • 在正文中,然后我对其进行解析并将其保存到数据中。 console.logs 运行良好,我只想使用从对象城市获得的不同参数对 API 进行循环。我认为我们需要使用 Promise,但我对它不太熟悉,这给我带来了很多麻烦。

标签: javascript node.js api csv object


【解决方案1】:

我建议使用 response-promise-native 来允许使用 async / await。这将允许我们遍历城市列表并将每个城市的天气数据附加到城市详细信息(名称和国家)。

一旦我们有了这些数据,我们就可以进行您提到的处理,我们可以获得最高和最低温度(请注意,温度以开尔文为单位,因此我们将转换为摄氏度。)

重要的是要指出我是按本地日期分组的,如果你想按utc日期分组,那么你应该换行:

let timeOffset = entry.dt + result.weatherResponse.city.timezone;

let timeOffset = entry.dt;

这是一种稍微不同的数据解释方式!

我现在已更新为按日期分组,结果如下所示:

按本地日期分组:

Date,Highest Temperature,Lowest Temperature,Cities With Rain
2019-11-01,Dubai,Oslo,"Paris,Berlin"
2019-11-02,Singapore,Oslo,"Lisbon,Paris,Berlin,Singapore"
2019-11-03,Singapore,Oslo,"Lisbon,Paris,Berlin,Athens,Singapore"
2019-11-04,Singapore,Oslo,"Lisbon,Paris,Berlin,Athens"
2019-11-05,Singapore,Oslo,"Lisbon,Paris,Berlin,Singapore"
2019-11-06,Singapore,Oslo,"Paris,Berlin,Singapore"
2019-11-07,Seoul,Seoul,""

按 UTC 日期分组:

Date,Highest Temperature,Lowest Temperature,Cities With Rain
2019-11-01,Dubai,Oslo,"Paris,Berlin"
2019-11-02,Singapore,Oslo,"Lisbon,Paris,Berlin,Singapore"
2019-11-03,Singapore,Oslo,"Lisbon,Paris,Berlin,Athens,Singapore"
2019-11-04,Singapore,Oslo,"Lisbon,Paris,Berlin,Athens"
2019-11-05,Singapore,Oslo,"Lisbon,Paris,Berlin,Singapore"
2019-11-06,Singapore,Oslo,"Paris,Berlin,Singapore"

代码:

const rp = require("request-promise-native");

const cities = {
    0: ['Jerusalem', 'il'],
    1: ['New York', 'us'],
    2: ['Dubai', 'ae'],
    3: ['Lisbon', 'pt'],
    4: ['Oslo', 'no'],
    5: ['Paris', 'fr'],
    6: ['Berlin', 'de'],
    7: ['Athens', 'gr'],
    8: ['Seoul', 'kr'],
    9: ['Singapore', 'sgp'],
}


async function getWeatherForCities() {
    let results = [];
    for (let [city, countryCode] of Object.values(cities)) {
        console.log(`Getting weather for city: ${city}, country: ${countryCode}...`);
        let weatherResponse = await rp({ url: `http://api.openweathermap.org/data/2.5/forecast?q=${city},${countryCode}&mode=json&appid=${apiKey}`, json: true});
        results.push ({ city, countryCode, list: weatherResponse.list, weatherResponse });
    }

    let summary = results.map(res => {  
        return { city: res.city, countryCode: res.countryCode,
        maxTemperature: getMaxTemperatureCelsius(res.list),
        minTemperature: getMinTemperatureCelsius(res.list),
        totalRainfall: getTotalRainFall(res.list)
    }});

    console.log("Summary (over forecasting interval): ", summary);
    console.log("Result with the highest temperature: ", [...summary].sort((resA, resB) => resB.maxTemperature - resA.maxTemperature)[0]);
    console.log("Result with the lowest temperature: ", [...summary].sort((resA, resB) => resA.minTemperature - resB.minTemperature)[0]);
    console.log("Cities with rain: ", summary.filter(res => res.totalRainfall).map(res => res.city));

    // Group by date (local) and city
    let resultsGroupedByDateAndCity = {};
    results.forEach(result => {
        result.list.forEach(entry => {
            let timeOffset = entry.dt + result.weatherResponse.city.timezone;
            let date = new Date(timeOffset * 1000);
            date.setHours(0,0,0,0);
            let dateKey = date.toISOString().substring(0,10);
            if (!resultsGroupedByDateAndCity[dateKey]) resultsGroupedByDateAndCity[dateKey] = {};
            if (!resultsGroupedByDateAndCity[dateKey][result.city]) resultsGroupedByDateAndCity[dateKey][result.city] = [];
            resultsGroupedByDateAndCity[dateKey][result.city].push(entry);
        });
    });

    // Run through the keys.
    let csvLines = ["Date,Highest Temperature,Lowest Temperature,Cities With Rain"];

    for (let [date, obj] of Object.entries(resultsGroupedByDateAndCity)) {
        let dailySummary = Object.entries(obj).map(([city, dayList]) => {  
            return { city,
            maxTemperature: getMaxTemperatureCelsius(dayList),
            minTemperature: getMinTemperatureCelsius(dayList),
            totalRainfall: getTotalRainFall(dayList)
        }});

        console.log("Details for date " + date + ": ");
        let resultWithHighestTemperature = [...dailySummary].sort((resA, resB) => resB.maxTemperature - resA.maxTemperature)[0];
        let resultWithLowestTemperature = [...dailySummary].sort((resA, resB) => resA.minTemperature - resB.minTemperature)[0];
        let citiesWithRain = dailySummary.filter(res => res.totalRainfall).map(res => res.city);
        console.log("Result with the highest temperature: ", resultWithHighestTemperature);
        console.log("Result with the lowest temperature: ", resultWithLowestTemperature);
        console.log("Cities with rain: ", citiesWithRain);

        csvLines.push([date, resultWithHighestTemperature.city, resultWithLowestTemperature.city, '"' + citiesWithRain.join(",") + '"'].join(","));
    }

    console.log("CSV result:\n", csvLines.join("\n"));
}

function KelvinToCelsius(kelvin) {
    return (kelvin - 273.15);
}

// Return the max temperature for the forecast
function getMaxTemperatureCelsius(responseList) {
    // Get a list of the max temperatures for the forecast.
    const maxTemps = responseList.map(entry => Number(entry.main.temp_max));
    return KelvinToCelsius(Math.max(...maxTemps));
}

// Return the min temperature for the forecast
function getMinTemperatureCelsius(responseList) {
    // Get a list of the min temperatures for the forecast.
    const minTemps = responseList.map(entry => Number(entry.main.temp_min));
    return KelvinToCelsius(Math.min(...minTemps));
}

// Return the total rainfall for the forecast
function getTotalRainFall(responseList) {
    // Get a list of the min temperatures for the forecast.
    const rain = responseList.map(entry => { return entry.rain ? Number(entry.rain["3h"]): 0 });
    return rain.reduce((sum, val) => sum + val, 0)
}

getWeatherForCities();

【讨论】:

  • 这个答案给了我迄今为止最好的解决方案,顺便说一句,我该如何操作它。
  • 我添加了函数来获得最大值。和分钟。每个城市的温度,我将添加一些功能来按最高温度排序城市,分钟。温度。等等!
  • 每个响应将为我们提供 40 个数据点,每天最多 8 个。这通常意味着我们将获得涵盖 6 天(2 部分 + 4 完整)的数据,例如 5、8、8、8、8、3。我认为将天气分配给一天的最可靠方法是使用 dt unix 时间戳并使用 moment.js 可能从中获取日期(moment.utc(dt))(我认为他们使用的是 UTC 时间!)如果我们只是跳过每 8 个数据点,我们就有可能将错误的日期分配给天气。
  • 啊,是的,我明白了你在做什么,你要间隔 1 天拍摄 5 天的快照。那可行!我的逻辑是从每个位置的 40 个读数中获取最大值(每个参数)。
  • 我明白你的意思,这是一个很好的逻辑,但我尝试了更简单的方法。如果您在发布的答案中更改了它,请告诉我,同时我将尝试弄清楚如何显示五个不同的日子并在每一天之间进行比较。这样我就可以将最终结果显示为 CSV 文件。
【解决方案2】:

您可以使用Promise.all 来实现此目的。

Promise.all 返回一个 Promise,当所有的 作为迭代传递的承诺已经解决或当迭代 不包含任何承诺。

const getData = (url) => {
   return fetch(url)
    .then(data => data.json())
    .then(jsonData => jsonData)
    .catch(err => {
      console.log("Error while resolving the promise for url", url);        
    });  
}

let arr = [1, 2, 4, 5, 6, 7];

const cities = {
    0: ['Jerusalem', 'il'],
    1: ['New York', 'us'],
    2: ['Dubai', 'ae'],
    3: ['Lisbon', 'pt'],
    4: ['Oslo', 'no'],
    5: ['Paris', 'fr'],
    6: ['Berlin', 'de'],
    7: ['Athens', 'gr'],
    8: ['Seoul', 'kr'],
    9: ['Singapore', 'sgp'],
}

const apiKey = "[retracted]";

Promise.all(Object.keys(cities).map(id => {  
  let url = `http://api.openweathermap.org/data/2.5/forecast?q=${cities[id][0]},${cities[id][1]}&mode=json&appid=${apiKey}`;
  return getData(url);
  }))
 .then(results => {

        // results is an array that contains the result of each api call
        // so you can perform the action that you need here..

        results.map(result => {

          console.log(result['city']['name']);

        });

  })
  .catch(err => {
        // Handle the error..
        console.log(err);
  });  

【讨论】:

  • 如果我在 Promise.all 中调用 API,fetch(URL) 的目的是什么?
  • @YonatanDarmon 我忽略了这一点,谢谢。编辑了getData 中的冗余承诺调用。
  • 你在citiesfetch逐一循环数据,然后Promise.all将所有数据作为一个promise返回。
  • 但是获取 URL 不是动态的,这让我只有一个响应。我需要动态地执行此操作,因此如果我更改数组中的城市,它将能够继续进行新的更改。我在我的问题之上添加了一个 API 示例
猜你喜欢
  • 2013-02-16
  • 2018-02-17
  • 1970-01-01
  • 2017-06-16
  • 2015-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多