Node.js 无法使用 Promise、Mongoose 和 GET 请求推送到全局数组

Posted

技术标签:

【中文标题】Node.js 无法使用 Promise、Mongoose 和 GET 请求推送到全局数组【英文标题】:Node.js Can not push to global array using promises, Mongoose, and GET requests 【发布时间】:2016-05-06 14:54:04 【问题描述】:

我无法将所需的结果推送到我在使用 Q Promise 库创建的同步函数中定义的数组。根据汽车品牌、用户位置的邮政编码和最大半径,此功能有 3 个任务:

    根据输入的特定制造商用户查询我的经销商集合以检索经销商 ID。 然后我定义一个数组:dealershipIDs。该数组将用于将某些经销商 ID 推送到。然后,我遍历返回的 json 经销商列表,以检索经销商的各个 ID 名称及其邮政编码。我向 api 服务发出 GET 请求,以计算用户输入的位置与任务 1 中找到的每个经销商之间的距离。如果经销商与用户之间的距离小于输入的半径,则将该经销商的 id 名称添加到数组中我想传递到第 3 步。我没有成功这样做,因为我尝试传递的数组是空的,并且不包含 for 循环之外的 id 名称。 在我的 Cars 集合中查询包含经销商 ID 列表的汽车。然后,最后一步将在用户区域呈现带有汽车结果的相应页面。

任务 2 是我的问题。我能够将正确的经销商 ID 添加到我定义的数组中,但我不能将该数组传递给下一个 .then,因为该数组在 for 循环之外是空的。

我已经被这个问题困扰了好几天了,我已经尝试了一切。请让我知道我是否可以更具体。

exports.getCarIndexPage = function(req, res) 
  var m = req.session.make; // User input
  var zipcode = req.session.zipcode; // User input
  var radius = req.session.radius; // User input
  req.session.valid = null; // Resets session variable

  Dealership.find(makes: m).exec()
    .then(function (ids) 
        var dealershipIDs = []; /* Trying to add dealer ids to array */
        ids.forEach(function (id) 
            var id = ids[i];
            getDistanceWithAPI(zipcode, id.zip, function(distanceVal) 
                console.log("This is the distance: " + distanceVal.distance);
                if (distanceVal.distance <= radius) 
                    console.log("Adding " + id._id + " to array");
                    dealershipIDs.push(id._id); // Adding dealership's id to array
                    console.log("Size of dealership array: " + dealershipIDs.length);
                   
                console.log("Inside for loop = Size of dealership array: " + dealershipIDs.length); /* Recognizes the array size! */
            )
        )
        console.log("Outside for loop = Size of dealership array: " + dealershipIDs.length); /* Does not recognize array size */
        return dealershipIDs; /* Return this array that contains the dealership ids */
    ).then(
        function (resultArray) 
            Car.find(dealership_id:  $in: resultArray ).exec()
                .then(function (cars) 
                    console.log(cars);
                ),
                function (error) 
                    console.log("Could not iterate through through cars: " + error);
                   
    , function (error) 
        console.error("Error with the outer promises:", error);
    );

如何通过添加到dealershipIDs 数组来使这个函数工作,以便我可以将它传递给用于查询我的Cars 集合?

以下函数是我的 HTTP 请求,它返回从 A 点到 B 点的距离的 JSON 对象。即(距离:1.664

function getDistanceWithAPI(userInput, dealerZip, callback) 
https.get('https://www.zipcodeapi.com/rest/xApFwnm4tosuL2gX2UDQIGcknN2NIHyfhXVNlhRPFkjrmzpou2edJry7fAVXhtdz/distance.json/' 
        + userInput + '/' + dealerZip + '/mile', function(res) 
  var body = ''; // Will contain the final response

  res.on('data', function(data)
    body += data;
  );

  // After the response is completed, parse it and log it to the console
  res.on('end', function() 
    var parsed = JSON.parse(body);
    callback(parsed); // i.e. returns distance : 1.664  
  );
)

// If any error has occured, log error to console
.on('error', function(e) 
  console.log("Got error: " + e.message);
);

这是我的日志:

Server running at http://localhost:3000/
Outside for loop = Size of dealership array: 0
[]
This is the distance: 1.664
Adding bmwofsf to array
Size of dealership array: 1
Inside for loop = Size of dealership array: 1
This is the distance: 13.685
Adding bmwofsanrafael to array
Size of dealership array: 2
Inside for loop = Size of dealership array: 2

【问题讨论】:

问题出在第二个任务中,getDistanceWithAPI 是一个异步函数。因此,第二个任务将在任何 getDistanceWithAPI 有机会运行之前快速返回。 能否包含失败运行的日志输出?您似乎没有等待getDistanceWithAPI 完成,然后才返回dealershipIDs @SimonMᶜKenzie Edit 包括失败运行的日志。 【参考方案1】:

我猜这个问题是因为在第二个任务中,getDistanceWithAPI 是一个异步函数。因此,第二个任务将在任何 getDistanceWithAPI 解析之前快速返回。让我尝试使用 preso 代码来解决下面的问题。它并不完美,因为它引入了一个全局数组,也许我们可以通过使用 Q.all 来改进它。

var dealershipIDs = []; /* put it outside, because the results in 2nd tasks is used to indicated the finished state.  */
Dealership.find(makes: m).exec()
    .then(function (ids) 
        var promises = []
        for (var i = 0; i < ids.length; i++) 
            var id = ids[i];
            promises.push(getDistanceWithAPI(zipcode, id.zip, function(distanceVal)  // Returns promise
                console.log("This is the distance: " + distanceVal.distance);
                if (distanceVal.distance <= radius) 
                    console.log("Adding " + id._id + " to array");
                    dealershipIDs.push(id._id); // Adding dealership's id to array
                    console.log("Size of dealership array: " + dealershipIDs.length);
                   
                console.log("Inside for loop = Size of dealership array: " + dealershipIDs.length); /* Recognizes the array size! */
            ));
        
        console.log("Outside for loop = Size of dealership array: " + dealershipIDs.length); /* Does not recognize array size */
        return Q.all(promises); // resolve all promises and return;
    ).then(
        function () 
            var resultArray = dealershipIDs;
            Car.find(dealership_id:  $in: resultArray ).exec()
                .then(function (cars) 
                    console.log(cars);
                ),
                function (error) 
                    console.log("Could not iterate through through cars: " + error);
                   
    , function (error) 
        console.error("Error with the outer promises:", error);
    );

【讨论】:

我喜欢你的答案。我没有想到定义一个承诺数组,然后使用'return Q.all(promises)'。无论出于何种原因,它都会继续挂断任务 2。我将继续探索 Q.all 建议并回复您。【参考方案2】:
exports.getCarIndexPage = function(req, res) 
var m = req.session.make;
var zipcode = req.session.zipcode;
var radius = req.session.radius;
req.session.valid = null; // Resets session variable

Dealership.find(makes: m).exec()
    .then(function (ids) 
        var promises = [];
        ids.forEach(function (id) 
            /* Pushing ascynchrounous functions into promise array */
            promises.push(getDistanceWithQPromise(zipcode, id.zip, id._id));
        );
        return Q.all(promises)
            .then(function (promise) 
                var dealershipIDs = []; /* Adding dealership ids to array */
                promise.forEach(function (promiseData) 
                    var distance = promiseData.distance;
                    var id = promiseData.id;
                    if (distance <= radius) 
                        console.log("Adding " + id + " to array");
                        dealershipIDs.push(id); // Adding dealership's id to array
                    
                );
                console.log("Outside for loop = Size of dealership array: " + dealershipIDs.length); /* Does recognize array size */
                return dealershipIDs;
            , function (err) 
                console.log(err)
            );
    ).then(function (resultArray)  // Receives the dealership Id array
            Car.find(dealership_id:  $in: resultArray ).exec()
                .then(function (cars) 
                    renderResult(res, req, cars);
                ),
                function (error) 
                    console.log("Could not iterate through through cars: " + error);
                   
    , function (error) 
        console.error("Error with the outer promises:", error);
    );

必须修改我的 GET 请求,以便它使用 Q 库返回一个承诺。我还必须将经销商 ID 添加到返回的响应中,以便我可以在 getCarIndexPage 中将其作为承诺值进行访问。

function getDistanceWithQPromise(userInput, dealerZip, dealerID) 
var deferred = Q.defer();
var request = https.request('https://www.zipcodeapi.com/rest/xApFwnm4tosuL2gX2UDQIGcknN2NIHyfhXVNlhRPFkjrmzpou2edJry7fAVXhtdz/distance.json/' 
        + userInput + '/' + dealerZip + '/mile', function(response) 
    var responseData = '';
    response.on('data', function (data) 
        responseData += data;
    );

    /* Adding the dealer ID to the response string so that I can convert it to JSON before returning the promise */ 
    response.on('end', function() 
        responseData = responseData.slice(0, -1);
        responseData += "," + '"id":' + '"'+dealerID+'"' + "";

        deferred.resolve(JSON.parse(responseData));
    );

    );

    request.on('error', function(err) 
            deferred.reject(err);
    );

    request.end();
    return deferred.promise;
;

非常感谢 Ron 建议将异步调用添加到 Promise 数组并使用 Q.all。这正是我所需要的。

【讨论】:

以上是关于Node.js 无法使用 Promise、Mongoose 和 GET 请求推送到全局数组的主要内容,如果未能解决你的问题,请参考以下文章

在node.js中使用promise处理MySQL返回值

如何在 node.js 中实际使用 Q Promise?

我们如何在(Node.js 8. 目前)上的 readline.on 函数下使用 promise

使用 Mongoose 的 Node.js 和 MongoDB。无法使用 findByIdAndUpdate 增加文档版本

使用 Mongoose 的 Node.js 和 MongoDB。无法使用 findByIdAndUpdate 增加文档版本

Node.JS:写入文件时,Promise 产生 null