Addendum to issue #94, as there is no dependency on lambda breaking the asynchronous callback structure in the code.
Dialogflowseems to be failing to follow the async flow of the most basic example I could produce:

This code has some log statements and sets some setTimeout calls to add a response to the agent after 5 seconds. after 10 seconds, another setTimeout will log some output.
The invocation looks like so: _note the 'Not available'_

And the logs look like so:

Note here that the most recent response is at the top, and that "Function execution took X ms, finished with status code: Y" is before the setTimeout functions return. The effect of this is that long calls (such as pinging API endpoints), will not have their values returned by the time the agent closes. This contradicts my basic understanding that execution is not yielded until all promises have been resolved in the function.
Any and all help would be appreciated
It turns out there's a workaround to this, where you wrap the entire function in a promise if you have any async calls, like so:

I believe the initial issue still stands, as the function shouldn't yield. But for other developers looking to just have a working solution now, do something like the above
JavaScript is asynchronous and a promise needs to be returned by the handler function in order for statements that alter the response to affect the response.
In your first code snippet the function is implicitly returned before the statements in the setTimeout blocks are executed. Since the function has finished executed the library then takes any responses added (in your case no responses were added) and responds to the Dialogflow webhook request. By the time the agent.add(...) statement is run the response has already been sent to Dialogflow without the response. Returning a promise for any async operations is the intended design.
The function reaches the last line in the file, as the log statement notes, but it is not finished executing, as there鈥檚 still outstanding code to be run.
My confusion here is that an agent function seems to act either as a synchronous or async function depending on the situation. Is that the intended design? Because it wasn鈥檛 mentioned in the starter guide or the sample apps I checked
"The function reaches the last line in the file, as the log statement notes, but it is not finished executing, as there鈥檚 still outstanding code to be run."
In your first example the function has returned before any of the setTimeout statements has run. You're right that the function hasn't "finished running" but the function has also returned. The library takes returning as a indication that you are done adding responses. If you are not done adding responses when the handler function has returned you need to wrap you async operations in a promise and return that promise so the library knows when you're async operations have finished and all the responses have been added.
It turns out there's a workaround to this, where you wrap the entire function in a promise if you have any async calls, like so:
I believe the initial issue still stands, as the function shouldn't yield. But for other developers looking to just have a working solution now, do something like the above
Huge props to @kylebegovich for this solution - we've been using promises elsewhere, but wrapping the function fed to the agent in a promise works well, even if you have to use a throwaway resolve() function at the end.
I really think Dialogflow need to address this issue. For example:
``
function playMusic(agent) {
return new Promise((resolve, reject) => { // this is the workaround
let music = request.body.queryResult.parameters["music"];
agent.add(Trying to play ${music}...);
spotify
.searchv2(music)
.then(result => { // spotify promise
smartthings.sonos("playTrack", result).then(result => { // sonos promise
agent.add(result);
resolve(); // this is for the workaround
});
})
.catch(error => {
agent.add(Error: ${error}`);
resolve(); // this is for the workaround
});
});
}
As mentioned previously, returning a promise for any async operation is the intended design.
what about here ? agent . add is not working at run 2
function fallback(agent) {
return translate.translate(userMessage, options)
.then(results => {
const translation = results[0];
return run1(translation, userMessage, agent);
})
.catch(error => {
console.error(error);
agent.add("Sorry, I couldn't translate that!");
agent.add("Could you try again?");
return resolve();
});
}
async function run1(agent) {
// A unique identifier for the given sessionromanianText
return new Promise((resolve, reject) => {
// bot call
sessionClient.detectIntent(request).then(results => {
const result = results[0].queryResult;
console.log(Detected intent ${JSON.stringify(result.intent.displayName)});
if (result.intent.displayName !== "Default Fallback Intent") {
agent.add(result.fulfillmentText);
return resolve();
} else {
return run2(agent);
}
})
.catch(error => {
console.error(error);
agent.add("Sorry, I couldn't translate that!");
agent.add("Could you try again?");
return resolve();
});
});
}
async function run2(agent) {
return new Promise((resolve, reject) => {
sessionClient.detectIntent(request).then(results => {
const result = results[0].queryResult;
agent.add(result.fulfillmentText);
return resolve();
}).catch(error => {
console.error(error);
agent.add("")
return resolve();
});
});
}