Dialogflow V2(Node.js)调用外部REST API前需提前设置响应问题求助
解决Dialogflow V2意图中异步API调用后返回响应的问题
看起来你遇到的核心问题是异步操作的处理方式不对——Dialogflow的意图处理函数需要明确告知系统要等待异步操作完成后再返回响应,而你当前用的request回调是“fire-and-forget”的模式,函数会直接执行完回调以外的代码就结束,导致Dialogflow认为你没有返回响应,提前要求输出。
问题根源
传统的request回调函数是异步执行的,当你的getWelcomeMsg函数执行到request.get()这一行时,会立即继续往下走(而不是等待API返回),但函数本身没有返回任何Promise或者响应,所以Dialogflow会触发“需要设置响应”的错误,而此时你的API请求还在后台跑呢。
解决方案
我们需要把异步的API调用包装成Promise,让Dialogflow知道要等待这个异步操作完成后再处理响应。有两种常用方式:
方式1:使用request-promise替代request
request-promise是request的Promise化版本,能直接返回Promise,配合async/await使用更简洁(你的Node.js 8.11.2已经支持async/await,6.10可以用Promise链)。
首先安装依赖:
npm install request-promise
然后修改代码:
const functions = require('firebase-functions'); process.env.DEBUG = 'actions-on-google:*'; const { dialogflow, SimpleResponse } = require('actions-on-google'); const rp = require('request-promise'); // 替换成request-promise const app = dialogflow(); // 把意图函数改成async函数,用await等待API响应 app.intent('Default Welcome Intent', async (conv) => { console.log("Token in Welcome: "+conv.user.access.token); const auth = "Bearer "+ conv.user.access.token; const snQuery = "api/now/table/sys_user?sysparm_query=sys_id=javascript:gs.getUserID()"; const endpointUrl = baseURL + snQuery; console.log("Endpoint user info: "+endpointUrl); try { // 用rp.get替代request.get,await等待结果 const body = await rp.get({ url: endpointUrl, headers: { "Authorization": auth, "Accept": "application/json", "Content-Type": "application/json" }, json: true // 自动把返回的JSON转成对象,不用手动JSON.parse }); // 存储用户信息 conv.user.storage.UserDetails = body; console.log("SYS ID_Welcome: "+conv.user.storage.UserDetails.result[0].sys_id); // 等待asyncTask完成后返回响应 await asyncTask(); conv.ask(new SimpleResponse({ speech: 'Howdy! tell you fun facts about almost any number, like 42. What do you have in mind?', text: 'Howdy! tell you fun facts about almost any number, like 42. What do you have in mind?', })); } catch (error) { // 处理API请求错误 const sText = " Sorry! I found some Issue in performing this action, Please try again after some time. "; console.log("Entering Error: "+error); conv.close(sText); } }); exports.MyFunctionName = functions.https.onRequest(app);
方式2:手动把request回调包装成Promise
如果你不想换依赖,可以自己封装Promise:
const functions = require('firebase-functions'); process.env.DEBUG = 'actions-on-google:*'; const { dialogflow, SimpleResponse } = require('actions-on-google'); const request = require('request'); const app = dialogflow(); // 先写一个封装request.get的Promise函数 function getRequest(options) { return new Promise((resolve, reject) => { request.get(options, (error, response, body) => { if (error) { reject(error); } else { resolve(body); } }); }); } // 然后在意图函数里使用 app.intent('Default Welcome Intent', (conv) => { console.log("Token in Welcome: "+conv.user.access.token); const auth = "Bearer "+ conv.user.access.token; const snQuery = "api/now/table/sys_user?sysparm_query=sys_id=javascript:gs.getUserID()"; const endpointUrl = baseURL + snQuery; console.log("Endpoint user info: "+endpointUrl); // 返回Promise,让Dialogflow等待 return getRequest({ url: endpointUrl, headers: { "Authorization": auth, "Accept": "application/json", "Content-Type": "application/json" } }) .then(body => { conv.user.storage.UserDetails = JSON.parse(body); console.log("SYS ID_Welcome: "+conv.user.storage.UserDetails.result[0].sys_id); return asyncTask(); // 等待asyncTask完成 }) .then(() => { conv.ask(new SimpleResponse({ speech: 'Howdy! tell you fun facts about almost any number, like 42. What do you have in mind?', text: 'Howdy! tell you fun facts about almost any number, like 42. What do you have in mind?', })); }) .catch(error => { const sText = " Sorry! I found some Issue in performing this action, Please try again after some time. "; console.log("Entering Error: "+error); conv.close(sText); }); }); exports.MyFunctionName = functions.https.onRequest(app);
关键要点
- Dialogflow的意图处理函数必须返回一个Promise(或者用async函数,因为async函数默认返回Promise),这样系统才会等待异步操作完成。
- 不要用传统的回调方式处理异步请求,否则函数会提前结束,导致响应设置不及时。
- 错误处理一定要放在Promise的catch或者try/catch里,避免未捕获的异常导致应用崩溃。
内容的提问来源于stack exchange,提问作者Rahil B Rasheed
相关产品推荐
相关产品推荐

