当调用函数并分配给 let 是唯一可见的问题时,如何解决 "NaN" 错误

How to trouble shoot "NaN" error when the call to a function and assigning to let is the only visible issue

我正在本地测试 Alexa 技能并收到一个仅显示 NaN 的错误。我已经通过 console.log() 语句发现第 let recipe = getRecipe() 行是问题所在。它似乎不在 getRecipe() 函数本身中,因为该函数中 try 块最开头的 console.log() 语句不是 运行,而是开头的语句的捕获量确实如此。在此先感谢您的任何建议。

处理程序:

    handle(handlerInput){
    const attributes = handlerInput.attributesManager.getSessionAttributes();
    const request = handlerInput.requestEnvelope.request;
    switch (attributes.previousIntent){
      case "FoodIntent":
        
        if(request.intent.slots.answer.resolutions.resolutionsPerAuthority[0].values[0].value.name === 'yes'){
          let randomFood = Helpers.suggestFood(handlerInput);         
          let queryFood = randomFood.replace(/\s+/g, '-').toLowerCase(); event
          attributes.currentSuggestedFood = queryFood;           
          const speechText = 'Great! In the future I will be able to look up the ingredients for you.'
          console.log('before call getRecipe()')
          let recipe = getRecipe(handlerInput)
          console.log('After call getRecipe()')

          return handlerInput.responseBuilder
          .speak(speechText + " "+ recipe)
          .reprompt(speechText)
          .withShouldEndSession(true)
          .withSimpleCard('Cheer Up - YesNo', speechText)
          .getResponse();
          
        } else {
          let randomFood = Helpers.suggestFood(handlerInput);
          let speechText = ResponseToUsersNo[Math.floor(Math.random() * ResponseToUsersNo.length)]+ 
                            FoodPrefixes[Math.floor(Math.random() * FoodPrefixes.length)] + 
                            randomFood + FoodSuffixes[Math.floor(Math.random() * FoodSuffixes.length)];
          let repromptText = 'Did the last suggestion work for you?'
          handlerInput.attributesManager.setSessionAttributes(attributes);
          
          if (attributes.FoodsAlreadySuggested.length >= 10) {
            speechText = 'I feel like you don\'t actually want anything. So I\'m leaving for now, talk to you later.'
            return handlerInput.responseBuilder
            .speak(speechText)
            .withShouldEndSession(true)
            .withSimpleCard('Cheer Up - YesNo', speechText)
            .getResponse();
          }
          
          return handlerInput.responseBuilder
          .speak(speechText)
          .reprompt(repromptText)
          .withSimpleCard('Cheer Up - YesNo', speechText)
          .getResponse();
        }
      case "HobbyIntent":
         
        if(request.intent.slots

和 getRecipe() 函数:

 async function getRecipe(handlerInput) {

  try{
    console.log('before attributes')
    const attributes = handlerInput.attributesManager.getSessionAttributes();
    console.log('attributes: '+ attributes)
    console.log('before url')
    const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`;  //&from=0&to=3&calories=591-722&health=alcohol-free   this was on the end of the uri
    console.log(url)
    console.log('after url')
    request.get(url, (error, response, body) => {
      // let json = JSON.parse(body);
      console.log('error:', error); // Print the error if one occurred
      console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
      console.log('body:', body); // Print the body

      //const theRecipe = await body;
      const payload = JSON.parse(body)
      console.log("The ingredients for "+ payload.q + " is: ")
      console.log(payload.hits[0].recipe.ingredientLines)

      return (payload.hits[0].recipe.ingredientLines);

  });
  }
  catch(err){
    console.log('before error statement in catch')
    console.error('There was an error: ', + err)
  }  
};

这是我的输出:

    before call getRecipe()
before attributes
attributes: [object Object]
before url
https://api.edamam.com/search?q=rellenos-de-papa&app_id=b4dbea92&app_key=8d916c99b930b77c8cbb4615f0800df7
after url
before error statement in catch
There was an error:  NaN
After call getRecipe()
{ version: '1.0',
  response:
   { outputSpeech:
      { type: 'SSML',
        ssml: '<speak>Great! In the future I will be able to look up the ingredients for you. The ingredients are [object Promise]</speak>' },
     reprompt: { outputSpeech: [Object] },
     shouldEndSession: true,
     card:
      { type: 'Simple',
        title: 'Cheer Up - YesNo',
        content: 'Great! In the future I will be able to look up the 
    ingredients for you.' } },
     userAgent: 'ask-node/2.3.0 Node/v8.12.0',
     sessionAttributes:
   { foodType: 'PuertoRican',
     FoodsAlreadySuggested: [ 'Platanos Maduros', 'Rellenos de Papa' ],
     previousIntent: 'FoodIntent',
     state: '_YES_NO',
     currentSuggestedFood: 'rellenos-de-papa' } }

更新:

@雪莉。所以我仍然很困惑......顺便说一句,我不得不稍微编辑你的函数以使捕获中的代码可以访问......但无论如何我认为我所做的仍然保留了你试图传授的核心逻辑。 我的问题是在解析 unexpected token o in JSON at position 1 时出现错误。我认为这通常意味着我不需要解析它,因为它已经是一个有效的 js 对象。凉爽的。所以我删除了解析,但随后我得到 Cannot read property '0' of undefined.,当然指的是我的 return payload.hits[0].recipe.ingredientLines。似乎无法理解为什么。非常感谢您的帮助。

function getRecipe(handlerInput) {
  const attributes = handlerInput.attributesManager.getSessionAttributes();
  const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`;  //&from=0&to=3&calories=591-722&health=alcohol-free   this was on the end of the uri
  return get( url, ( response, body ) => {
    const payload = JSON.parse(body)
    console.log(payload)
    return payload.hits[0].recipe.ingredientLines;
  }).catch( error => {
    console.error( `failed GET request for: ${ url }` );
    console.error( error );
  }); 
};

另外这里是响应正文的开头,我看没有解析...body: '{\n "q" : "tostones",\n "from" : 0,\n "to" : 10,\n "params" : {\n

const attributes = handlerInput.attributesManager.getSessionAttributes() 抛出 NaN 错误,原因不明。

catch() 处理程序捕获此错误并将其记录下来。 但是由于该函数是异步的,并且您没有解析 catch 子句中的承诺,因此您得到的是该承诺的这个 [object Promise] 字符串化版本,而不是实际的成分列表。

编辑:

您是否考虑过使用 util.promisify() 这样您就不必混用回调和承诺?

const { promisify } = require( 'util' );
const request = require( 'request' );
const get = promisify( request.get );

function getRecipe(handlerInput) {
  const attributes = handlerInput.attributesManager.getSessionAttributes();
  const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`;  //&from=0&to=3&calories=591-722&health=alcohol-free   this was on the end of the uri
  return get( url, ( response, body ) => {
    const payload = JSON.parse(body)
    return payload.hits[0].recipe.ingredientLines;
  }).catch( error ) {
    console.error( `failed GET request for: ${ url }` );
    console.error( error );
  }); 
};

同样可以用 async/await 风格编写,但我不够流利,无法在无法自己测试代码的情况下使其 100% 正确。

终于想通了。非常感谢@Shilly 指导我正确的方向。我对异步和等待的理解是错误的。这些资源很有帮助:

Returning handler.ResponseBuilder from promise.then() method

https://medium.com/@tkssharma/writing-neat-asynchronous-node-js-code-with-promises-async-await-fa8d8b0bcd7c

这是我更新后的代码:

异步处理程序依赖于我在@Shilly 的帮助下创建的使用 Promises 的函数。这可能不是最简洁的方法,但它确实有效!

处理程序:

async handle(handlerInput){ 
    const attributes = handlerInput.attributesManager.getSessionAttributes();
    const request = handlerInput.requestEnvelope.request;
    switch (attributes.previousIntent){
      case "FoodIntent":

        if(request.intent.slots.answer.resolutions.resolutionsPerAuthority[0].values[0].value.name === 'yes'){
          let randomFood = Helpers.suggestFood(handlerInput);         
          let queryFood = randomFood.replace(/\s+/g, '-').toLowerCase(); 
          attributes.currentSuggestedFood = queryFood;           
          const speechText = 'Great! Here are the ingredients!'
          let recipe = await getRecipe(handlerInput)
            let recipeIngredients = recipe.hits[0].recipe.ingredientLines;
            return handlerInput.responseBuilder
            .speak(speechText+ 'The ingredients are '+ recipeIngredients)
            .reprompt(speechText)
            .withShouldEndSession(true)
            .withSimpleCard('Cheer Up - YesIntentFood', recipeIngredients)
            .getResponse();

函数:

async function getRecipe(handlerInput) {
  const attributes = handlerInput.attributesManager.getSessionAttributes();
  const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`;
  console.log(url)
  return new Promise (function(resolve, reject) {
    request.get(url, (error, response, body) => {
      if (error) {
        reject(error);
      } else {
          resolve(JSON.parse(body))
      }
  });
})
};

输出:

https://api.edamam.com/search?q=pernil&app_id=b4dbea92&app_key=8d916c99b930b77c8cbb4615f0800df7
{ version: '1.0',
  response:
   { outputSpeech:
      { type: 'SSML',
        ssml: '<speak>Great! In the future I will be able to look up the ingredients for you.The ingredients are 2 1/2 pounds pork shoulder, boston butt, pernil,2 garlic cloves,1 small onion,1 bunch cilantro,1 jalapeño,1 cup orange juice,1 cup pineapple juice,1 lemon,Handfuls salt,Pepper to taste,Ground cumin</speak>' },
     reprompt: { outputSpeech: [Object] },
     shouldEndSession: true,
     card:
      { type: 'Simple',
        title: 'Cheer Up - YesIntentFood',
        content: [Array] } },
  userAgent: 'ask-node/2.3.0 Node/v8.12.0',
  sessionAttributes:
   { foodType: 'PuertoRican',
     FoodsAlreadySuggested: [ 'Platanos Maduros', 'Pernil' ],
     previousIntent: 'FoodIntent',
     state: '_YES_NO',
     currentSuggestedFood: 'pernil' } }