功能在过滤前过早返回并减少完成

时间:2018-03-16 13:53:08

标签: javascript actions-on-google dialogflow

我有以下函数,它应该通过数组检查,总结一些值,然后返回一个带总和的字符串(对于Dialogflow / Google Assitant):

function howMuch(app) {
return bank.getTurnovers(accessToken)
    .then((result) => {
        const { turnovers } = JSON.parse(result);

        let sum = turnovers
            .filter((i) => {
                if (i.tags.includes(tag)) {
                    console.log(i); // correctly prints relevant elements
                    return i;
                }
            })
            .reduce((prev, j) => {
                console.log(prev, j.amount) // correctly prints 0 7, 7 23
                return prev + j.amount;
            }, 0);

        console.log(sum); // prints sum correctly: 30
        return app.ask('You have spend ' + sum); // returns 0 for sum
    })
    .catch((error) => {
        // handle error
    });
};

问题是,该函数只返回You have spend 0,其中0是reduce函数中设置的初始值,但总和实际为30.它似乎不等待reduce完成。 这有什么问题?

2 个答案:

答案 0 :(得分:1)

恭喜,您发现了人们在使用Promises时遇到的最大误解:Promise可以为其调用上下文返回可预测的值。

正如我们所知,我们传递给new Promise(callback)构造函数的回调接收两个参数,每个回调本身,预计将使用

调用
  1. resolve(result) - 异步操作的结果
  2. 拒绝(错误) - 操作失败时的错误条件
  3. 假设我们定义了一个返回包装在对象中的字符串消息的简单函数:

    /**
     * @typedef {Object} EchoResult
     * @property {String} message
     *
     * @example
     *    {message: 'this is a message'}
     */
    
    /**
     * Wraps a message in a EchoResult.
     * @param  {String} msg - the message to wrap
     * @return {EchoResult}
     */
    const echo = function (msg) {
      return {
        message: msg
      }
    }
    

    致电

    const result = echo('hello from echo!')
    
    console.log(result)
    

    我们可以期待

    { message: 'hello from echo!'}
    

    打印到控制台。

    到目前为止一切顺利。

    现在让我们用Promise做同样的事情:

    /**
     * Wraps a msg in an object.
     * @param  {String} msg - the message to wrap
     * @return {Promise.<EchoResult>}
     */
    const promised_echo = function (msg) {
      return new Promise((resolve, reject) => {
        resolve({message: msg})
      })
    }
    

    调用此函数将返回一个Promise,我们希望最终解析为EchoResult,以便通过调用

    promised_echo('hello from promised echo!')
      .then(res => {
        console.log(res)
      })
    

    我们可以期待

    { message: 'hello from promised echo!'}
    

    打印到控制台。

    哇哇! *凯旋宣传*我们使用了承诺!

    现在出现困扰很多的人。

    您希望我们的promised_echo函数返回什么?让我们来看看。

    const result =
      promised_echo('returns a Promise!')
    
    console.log({result})
    

    烨!它返回Promise

    { result: Promise { <pending> } }
    

    这是因为我们定义了上面的promised_echo函数来做到这一点。容易腻。

    但是,你期望promised_echo.then()函数链接的是什么?

    const result =
      promised_echo('hello from a Promise!')
        .then(msg => {
          return msg
        })
    
    console.log({result})
    

    哦!惊喜!它还返回一个承诺!

    { result: Promise { <pending> } }
    

    不要害怕!如果您希望它返回字符串hello from a Promise!,那么您并不孤单。

    这很有道理,因为then()catch()函数(统称为&#34; thennables&#34;)被设计为链接到Promises,并且您可以链接多个thenables根据定义,对于承诺,不能将任何可用的,可预测的值返回到其调用上下文中!

    在OP的示例代码中,您有

    .then((result) => {
      // ... 
      console.log(sum); // prints sum correctly: 30
      return app.ask('You have spend ' + sum); // returns 0 for sum
    }
    

    你的Promise链中的final finalnn尝试返回调用你的app.ask()函数的结果,正如我们所见,这个函数无法完成。

    因此,为了能够使用传递给thenable的值,似乎只能在内部使用thennable回调本身。

    这是所有 Promise-dom 唯一最大的误解

    &#34;但是等待!“,正如清晨电视小贩说的那样,”还有更多!&#34;

    另一种类型的.catch()函数呢?

    在这里,我们发现另一个惊喜!

    我们发现.then()函数确实完全,因为它无法返回值,而且无法抛出我们可以使用的错误在我们的代码中找到问题

    让我们重新定义promised_echo而不是非常失败:

    const bad_promised_echo = function (msg) {
      return new Promise((resolve, reject) => {
        throw new Error(`FIRE! FAMINE! FLEE!! IT'S TEOTWAWKI!`)
      })
    }
    // TEOTWAWKI = The End Of The World As We Know It!
    

    这样,跑步

    bad_promised_echo('whoops!')
    

    将失败:

    (node:40564) UnhandledPromiseRejectionWarning: 
      Unhandled promise rejection (rejection id: 1): 
        Error: FIRE! FAMINE! FLEE!! IT'S TEOTWAWKI!
    
    等等,什么?

    我的stacktrace去了哪里?

    我需要我的堆栈跟踪,以便我可以确定错误发生在我的代码中的哪个位置! (&#34;我需要我的痛苦!它是什么让我,!&#34; - 从James T. Kirk转述。)

    我们失去了堆栈跟踪,因为从Promise的回调函数的角度来看,我们没有运行时上下文,我们可以从中派生它。 (更多内容见下文。)

    哦,我知道!我只是使用另一种类型的.catch()函数来捕获错误。

    你是这么认为的,但没有骰子!因为我们仍然没有任何运行时上下文我这次会保存示例代码。相信我,或者更好的是,亲自尝试一下! :)

    承诺在通常的程序控制流程之外运行,在异步运行的事物的昏暗世界中,我们期望的事情将在某个时间完成......

    (这是我击中我的高手科学家的姿势,用手臂抬起并用手指指向远方盯着距离,并且不祥地吟唱......)

    未来!

    *排队scwo scifi theremin music *。

    这是真正有趣的,但令人困惑的关于异步编程的事情,我们运行函数来实现一些目标,但我们无法知道何时他们将完成哪个是异步性的本质。

    考虑以下代码:

    request.get('http://www.example.com').then(result => {
      // do something with the result here
    })
    

    您可能希望它何时完成?

    根据几个不可知的因素,例如网络状况,服务器负载等, 无法 知道获取请求何时完成,除非您有一些怪异的预知能力。

    为了说明这一点,让我们回到我们的回声示例,然后注入一些&#34;不可知性&#34;通过微笑推迟我们的承诺的解决方案进入我们的职能:

    let result
    
    const promised_echo_in_the_future = function (msg) {
      return new Promise(function(resolve, reject) {
        setTimeout(() => { // make sure our function completes in the future
          result = msg
          resolve({
            message: msg
          })
        }, 1) // just a smidge
      })
    }
    

    然后运行它(我想你现在已经知道了我的目标。)

    promised_echo_in_the_future('hello from the future!')
    
    console.log({result})
    

    由于我们的Promise的解析只是在我们的控制台日志功能尝试引用它之后发生的,我们可以期待

    { result: undefined }
    

    将在控制台上打印。

    好的,要消化很多,我赞赏你的耐心。

    我们还有一件事需要考虑。

    async/await怎么样?我们可以使用它们来公开异步函数的已解析值,以便我们可以在该函数的上下文之外使用它吗?

    不幸的是,没有。

    因为async / await实际上只是用于通过使出现同步操作来隐藏Promise的复杂性的语法糖,

    const async_echo = async function (msg) {
      let res = await promised_echo(msg)
      return res
    }
    
    let result = async_echo();
    
    console.log({result})
    

    会将预期结果打印到控制台

    { result: Promise { <pending> } }
    

    我希望这可以解决一些问题。

    使用Promise时我能给出的最好建议是:

    始终使用在链本身的回调中调用Promise链的任何已解析结果,并以相同的方式处理在Promise链中创建的任何错误。

答案 1 :(得分:0)

app.ask()会将响应发送给用户(并且,由于您使用的是ask()而非tell(),因此也说明了麦克风是否打开了。它正好返回Express'Response.send()方法返回的内容。

将字符串返回给调用函数。

如果您想要该值,则此时不应调用app.ask()。只需返回值(或最终解析为该值的Promise)并调用app.ask()