我有以下函数,它应该通过数组检查,总结一些值,然后返回一个带总和的字符串(对于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完成。
这有什么问题?
答案 0 :(得分:1)
恭喜,您发现了人们在使用Promises
时遇到的最大误解:Promise可以为其调用上下文返回可预测的值。
正如我们所知,我们传递给new Promise(callback)
构造函数的回调接收两个参数,每个回调本身,预计将使用
假设我们定义了一个返回包装在对象中的字符串消息的简单函数:
/**
* @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()
。