【问题标题】:Why am I not able to access the result of multiple networking calls?为什么我无法访问多个网络调用的结果?
【发布时间】:2017-07-09 01:15:56
【问题描述】:

在我的 Node 应用程序中,我正在尝试获取包裹的发货数据。我需要一种方法来获取 json 数据并将其附加到一个对象或其他东西上,以便我可以将它传递给我的渲染页面(使用 pug)。

这是我的代码:

    var test;

    for(var i = 0; i < result.length; i++) {
        var currentNumber = result[i].trackingNumber;
        ups.track(currentNumber, function(err, tracking) {
            test += tracking
        });
    }

    res.send(result)

似乎我在 ups.track 中所做的任何事情都没有进入 var 测试的范围,而且我想不出更好的方法来做到这一点。任何帮助将不胜感激。

【问题讨论】:

    标签: node.js express pug


    【解决方案1】:

    首先,ups.track() 是一个异步函数。这意味着它将在未来某个不确定的时间调用其回调。因此,您的 for 循环运行完成,启动 result.lengthups.track() 的调用,然后在它们中的任何一个完成之前执行 res.send(result),在它们中的任何一个调用回调之前。

    其次,您没有初始化 test 变量,因此 test += ... 不会产生好的结果。

    因此,在所有结果都完成之前,您无法知道结果。有多种方法可以做到这一点。

    低技术计数器

    做到这一点的“低技术”方法是保持计数器:

    var test = "";
    
    var cntr = 0;
    for(var i = 0; i < result.length; i++) {
        var currentNumber = result[i].trackingNumber;
        ups.track(currentNumber, function(err, tracking) {
            test += tracking;
            // see if we are done with all the ups.track() calls yet
            if (++cntr === result.length) {
                res.send(test);
            }
        });
    }
    

    使用承诺

    更高技术的方法是使用 Promise(由于许多其他原因,这对学习有更大的好处)。虽然 Promise 在这里只显示出适度的好处,但只要您有需要协调的异步操作序列,它们就会提供巨大的好处,并且它们还可以使错误处理(您在代码示例中忽略的事情)变得更加简单。

    首先,您创建一个“承诺”版本的ups.track(),这是一个返回承诺的函数,当ups.track() 完成时,该承诺将解决或拒绝。

    function upsTrack(num) {
        return new Promise(function(resolve, reject) {
            ups.track(num, function(err, tracking) {
               err ? reject(err) : resolve(tracking);
            });
        });
    }
    

    现在,我们将使用.map() 的组合从您对upsTrack()Promise.all() 的所有调用中创建一个数组或承诺,以告诉我们所有这些承诺何时完成。

    Promise.all(result.map(function(item) {
        return upsTrack(item.trackingNumber);
    })).then(function(allResults) {
        // allResults is an array of tracking results
        // process that array into your final result and send it
        res.send(allResults.join(","));
    }).catch(function(err) {
        // handle error here
        res.status(500).end();
    });
    

    请注意,此结构还消除了在以前的低技术方式中定义的多个变量,因为不再需要 testcntri。处理数组或处理结果数组的循环结构和函数为我们解决了所有这些问题。

    使用 Bluebird Promise 库

    而且,您甚至可以通过使用像 Bluebird 这样更智能的 Promise 库来走更多捷径:

    const Promise = require('bluebird');
    ups = Promise.promisifyAll(ups);
    
    Promise.map(result, function(item) {
        return ups.trackAsync(item.trackingNumber);
    }).then(function(allResults) {
        // allResults is an array of tracking results
        // process that array into your final result and send it
        res.send(allResults.join(","));
    }).catch(function(err) {
        // handle error here
        res.status(500).end();
    });
    

    Bluebird Promise 库在这里有两个有用的东西。它有一个promisifyAll() 方法,它会自动为你生成所有ups 对象方法的promisified 方法,它还有Promise.map(),它只是将array.map()Promise.all() 组合在一个步骤中(因为它是一种常用的结构)。

    如果您对 Promise 和 Promise 库感兴趣,您可能会发现这很有用:

    Are there still reasons to use promise libraries like Q or BlueBird now that we have ES6 promises?

    【讨论】:

    • 看,在这里我认为你的if (err) return reject(err); resolve(tracking); 更令人不安,就选择的代码库风格而言。你有一个隐式else和一个隐式return,使用显式return代替大括号,与显式ifreturn不同,隐式else和@987654352 @ 发生在语句序列的不同点。这就是我看到即使是优秀的开发人员在扩展或修复其他错误时也写了可怕的错误的地方,但由于重构时的意图模糊不清,几个小时(或几天)都没有找到它。
    • @Norguard - 所以你永远不会写有returnif 语句?真的吗?这段代码一点也不难读。如果有更多的事情发生,我会使用大括号将它们挡住,但这真的很简单,因此不需要可读性或维护。但是,对于这种情况,我不介意你的三元风格,所以我修改了我的风格。
    • 我不会说永远不会。但我从不混合战术,也许在更长的函数顶部的一些保护条款之外。我使用大括号(和分号),因为在这种情况下,简短的杀戮。至少它杀死了我所在的团队。如果每个人都一直休息,这是我们在项目中使用的常见风格,我们都会笑着忍受,并更新我们的 linter 和 formatter 以匹配。但我已经看到这些东西在更大的重构中走向南方。
    【解决方案2】:

    第一个问题是test开头是undefined。那很糟。您想要一个与您期望的相同类型的默认值var test = "";

    不过,最大的问题是您在任何 ups 回调返回之前发送。

    您已经向 UPS 发出了 18 个(或其他)请求,但在 UPS 服务器收到第一个请求之前,您已经返回了 result,更不用说完成所有请求了。

    与其向您展示可能编写以使事情在您的循环中工作的真正毛茸茸的代码,我建议您尝试这样的事情:

    const track = trackingNumber =>
      new Promise((resolve, reject) =>
        ups.track(trackingNumber, (err, data) =>
          err ? reject(err) : resolve(data)));
    
    Promise.all(result.map(info => track(info.trackingNumber)))
      .then(trackingNumbers => trackingNumbers.join(''))
      .then(bigStringOfNumbers => res.send(bigStringOfNumbers));
    

    了解 Promises、Promise.all()[].map() 将比尝试构建此解决方案的复杂回调版本更有帮助。

    【讨论】:

    • 人们真的认为const track = trackingNumber =&gt; ... 是定义函数的好方法吗?如果是这样,我对 Javascript 的发展感到不安。这真的是你能写出的最好的可读代码吗?当您打算声明可重用函数时,使用 function 关键字会发生什么?
    • @jfriend00 函数声明既是提升的又是可变的。函数表达式已经在代码中使用了很长时间(作为对象属性或变量;导出的 ES5 IIFE 模块和可配置函数需要它,如果以纯方式完成)。如果我定义了一个identity 函数和一个noop 函数,哪种风格更易读?几乎所有字符都与函数上下文无关的样式,或者几乎所有字符都与函数的上下文无关?
    • 我不反对函数表达式(尽管它不是我最喜欢的)。我反对在定义可重用函数时在任何地方都缺少 function 关键字。你显然认为这是浪费空间。我发现它在扫描代码并快速识别它在做什么方面非常有用。我很失望人们选择在他们的 ES6 编码风格中完全放弃 function 关键字。我认为它在使代码更易于阅读方面非常有用。简洁不是我的特别目标。我宁愿拥有易于阅读的代码而不是简短的代码。
    • @jfriend00 在附录中,该函数不一定是可重用的。这是一个本地化的函数,它被提取出来,因此它的实现不需要在Promise.all 代码正在工作的抽象级别内可见。 Bob 叔叔和 Martin Fowler 支持任何语言的东西。此外,现在本地化的功能可以导出和测试(集成,而不是单元,由于它对ups 服务的期望,但仍然如此)。对于重用,签名比正文更重要。在 Flow 或 TS 中,这已涵盖。甚至只是 VSCode 中的原始 JS。
    • 老实说,function 什么也没告诉我。我的团队通常假设所有块都是函数,默认情况下没有任何东西运行。这与brief 关系不大,更多的是因为我们不需要在那里看到三个单独的function 关键字和三个单独的return 语句,这在编写所有的表达式时都会变得非常有害正是一件事。如果我打算说明一个看起来像 C 或 Java(或传统 JS)的传统代码库,我会紧跟在您身后,遵循这些风格。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-25
    • 1970-01-01
    • 2015-08-19
    • 1970-01-01
    • 1970-01-01
    • 2022-11-25
    • 2014-01-29
    相关资源
    最近更新 更多