让 Node 等到所有数据都加载完毕才发送响应

Let Node wait till all data is loaded to send response

情况示意图

我正在用 Node.js 和 and a 制作一个网络服务器。我将在我的 Android 应用程序的主页片段中将一些系列表示为一个列表。类似于下图:

红色矩形是一个包含多个系列的列表。

数据

在数据库中,我创建了一个名为 lists 的集合。这是一个文档示例:

{
    "_id" : ObjectId("486464a459f14e486012ee4a"),
    "name" : "Flemish",
    "series" : [ 61519, 64095, 11431, 16148, 63315, 68667, 8318, 61548, 62025, 36960 ]
}

属性 series 在每个文档中的长度不同。

该数组中的数字代表系列中的 ID。他们来自 The Movie Database (TMDb).

我会怎样

现在我将数据库中的列表与 TMDb 中的数据合并。为此,我编写了以下代码:

const dbService = require("./../data/databaseService.js"),
      apiService = require("./../data/apiService.js"),
      express = require("express"),
      router = express.Router();

router.get("/list", (req, res, next) => {

    dbService.getLists((err, data) => {
        if (err) {
            next(err);
        }
        else {

            let seriesData = [];

            for (var listIndex = data.length - 1; listIndex--;) {

                let temp = { 
                    name: data[listIndex].name, 
                    series: [] 
                };

                for (var seriesIndex = data[listIndex].series.length - 1; seriesIndex--;) {

                    let id = data[listIndex].series[seriesIndex];

                    apiService.request(`tv/${id}?append_to_response=images,similar`, (err, data) => {
                        if (err) {
                            next(err);
                        }
                        else {
                            temp.series.push(data);
                        }
                    });
                }

                seriesData.push(temp);
            }

            res.send(seriesData);
        }
    });
});

module.exports = router;

问题

我遇到的问题是行 res.send(seriesData); 在 数据被推送到数组 seriesData 之前被调用 。这发生在这条线上 temp.series.push(data);。您可以在下面找到将要发送的代码:

[
    {
        "name": "British",
        "series": []
    },
    {
        "name": "American",
        "series": []
    },
    {
        "name": "Reality",
        "series": []
    },
    {
        "name": "Flemish",
        "series": []
    }
]

我知道一切都与 Node.js 异步发生,我对结果并不感到震惊,但现在 Node 必须等待。

问题

现在我的问题是,响应是否可以等到所有数据从TMDb 加载完成?如果是如何,否则为什么?

另请注意,列表中的项目必须相等。示例:系列"Game of thrones" 位于美国列表中,不能被推到另一个列表中。与系列 "Als de dijken breken" 相同。该系列进入弗拉芒语列表,无法推入英国语。

参考

我制作了一张图片,您可以在其中再次检查代码并获得每个请求和响应的结果作为 JSON 代码。

(点击图片查看实际尺寸)

经过搜索和测试,我终于找到了解决方案。

图书馆

对于库,我使用 Async.js。您可以在命令行中使用以下代码来安装它:

npm install --save async

并使用这个添加库

const async = require("async");

我使用 async.each() 方法。

This is the simpler solution to the problem. The function takes an array of items, then iterates over them calling a wrapper function which accepts the item as an argument. When all the calls are complete, you specify a final function to be called.

// 1st para in async.each() is the array of items
async.each(items,
  // 2nd param is the function that each item is passed to
  function(item, callback){
    // Call an asynchronous function, often a save() to DB
    item.someAsyncCall(function (){
      // Async call is done, alert via callback
      callback();
    });
  },
  // 3rd param is the function to call when everything's done
  function(err){
    // All tasks are done now
    doSomethingOnceAllAreDone();
  }
);

Source: justinklemm.com - Node.js Async tutorial

提示:使用命名函数。

代码

您可以在下面找到我使用的代码:

const dbService = require("./../data/databaseService.js"),
      apiService = require("./../data/apiService.js"),
      express = require("express"),
      async = require("async"),
      router = express.Router();

router.get("/list", (req, res, next) => {

    dbService.getLists((err, data) => {
        if (err) {
            next(err);
        }
        else {
            
            let seriesData = [],
                apiRequests = [];

            for (let listIndex = data.length; listIndex--;) {

                let theName = data[listIndex].name;

                seriesData.push({
                    name: theName,
                    series: []
                });

                for (let seriesIndex = data[listIndex].series.length; seriesIndex--;) {

                    let id = data[listIndex].series[seriesIndex];

                    apiRequests.push({
                        destinationListName: theName,
                        tmdbId: id
                    });
                }
            }

            let apiCall = (apiReq, cb) => {
                apiService.request(`tv/${apiReq.tmdbId}?append_to_response=images,similar`, (err, data) => {
                    if (err) {
                        next(err);
                    }
                    else {
                        for (let listIndex = seriesData.length; listIndex--;) {
                            let name = seriesData[listIndex].name;

                            if (name == apiReq.destinationListName) {
                                seriesData[listIndex].series.push(data);
                                cb();
                            }
                        }
                    }
                });
            },
            afterApiCall = (err) => {
                if (err) {
                    next(err);
                }
                else {
                    res.send(seriesData);
                }
            };

            async.each(apiRequests, apiCall, afterApiCall);
        }
    });
});

module.exports = router;