Promise.all () и отлов ошибок

Я написал фрагмент кода, который получает JSON из Foursquare API. Из этого JSON я получаю идентификаторы заведений. Затем эти идентификаторы используются для получения дополнительных сведений об этих конкретных местах путем отправки запроса fetch() для каждого идентификатора и сопоставления этих запросов в массиве. Затем этот массив передается в Promise.all(). Когда API доступен, все работает, но это ошибка, которую я не могу понять.

fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
  .then(response => response.json())
  .then(data => {
      const venueIds = data.response.venues.map(venue => venue.id)

      const venuePromises = venueIds.map(venueId => {
        fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
          .then(response => {
            // Must check for response.ok, because 
            // catch() does not catch 429
            if (response.ok) {
              console.info('ok')
              return response.json()
            } else {
              Promise.reject('Error when getting venue details')
            }
          })
      })

      Promise.all(venuePromises).then(data => {
        const venues = data.map(entry => entry.response.venue)  // Error for this line
        this.parseFsqData(venues)
      }).catch((e) => {console.info(e); getBackupData()})
  }).catch((e) => {console.info(e); getBackupData()})

  function getBackupData() {
    console.info('backup')
  }

Когда API недоступен, я получаю следующие консольные ошибки (и многие другие):

TypeError: Cannot read property 'response' of undefined
    at MapsApp.js:97
    at Array.map (<anonymous>)
    at MapsApp.js:97

backup

api.foursquare.com/v2/venues/4b7efa2ef964a520c90d30e3?client_id=ANDGBLDVCRISN1JNRWNLLTDNGTBNB2I4SZT4ZQYKPTY3PDNP&client_secret=QNVYZRG0JYJR3G45SP3RTOTQK0SLQSNTDCYXOBWUUYCGKPJX&v=20180323:1 Failed to load resource: the server responded with a status of 429 ()

Uncaught (in promise) Error when getting venue details

Я не понимаю, почему вводится then() после Promise.all (), потому что response никогда не является ok (в консоли нет зарегистрированного ok). Кроме того, я не понимаю, почему console.info() в блоках catch() не выполняются или почему они пусты. Я не вижу информации об обнаруженных ошибках в консоли, но функция getBackupData все равно вызывается. Наконец, неясно, почему последнее сообщение в консоли указывает, что ошибка непойманный, поскольку я ожидал, что reject() приведет к сбою Promise.all().

Как я могу тактично обнаруживать любые ошибки (включая те, которые обычно не обнаруживаются catch(), например, ошибки 429) и вызывать getBackupData при возникновении каких-либо ошибок?

Вы не возвращаете этот Promise.reject('Error when getting venue details'), поэтому индекс venuePromises имеет значение undefined, а не отклоненное обещание.

tehhowch 14.08.2018 18:56

@tehhowch хороший ответ, который вы получили ..... в качестве комментария.

escapesequence 14.08.2018 19:02

@escapesequence выглядит как вопрос "из-за опечатки". Я не отвечаю на них. Теперь, когда я смотрю на код, есть еще несколько пропущенных операторов return, так что пора написать один.

tehhowch 14.08.2018 19:04

@tehhowch достаточно честно

escapesequence 14.08.2018 19:06
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
5
4
1 896
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Попробуйте вернуть отклоненное обещание.

return Promise.reject('Error when getting venue details')

В дополнение к вашему ответу, внутренний fetch() также должен быть возвращен функции map(). Ср. ответы теххоуча и Мартина Сарагосы о дополнительных исправлениях.

Bram Vanroy 14.08.2018 19:58

При работе с обещаниями вы должны возвращать внутренние обещания вместо работы с внутренними «тезисами».

Проверь это:

fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
  .then(response => response.json())
  .then(data => {
      const venueIds = data.response.venues.map(venue => venue.id);

      const venuePromises = venueIds.map(venueId => {
        fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
          .then(response => {
            // Must check for response.ok, because 
            // catch() does not catch 429
            if (response.ok) {
              console.info('ok')
              return response.json()
            } else {
              return Promise.reject('Error when getting venue details')
            }
          })
      });

      return Promise.all(venuePromises)
  })
  .then(venueValues => {
    const venues = venueValues.map(entry => entry.response.venue);  // Error for this line
    this.parseFsqData(venues);
  })
  .catch((e) => {console.info(e); getBackupData()})


function getBackupData() {
    console.info('backup')
}

Возвращая Promise.all в качестве значения, вы возвращаете обещание, чтобы вы могли связать дальнейшие обратные вызовы «затем». Последний улов должен захватить все отклоненные обещания.

Вам также не хватает возврата в предложении else

Надеюсь это поможет

В дополнение к вашему ответу, внутренний fetch() также должен быть возвращен функции map(). Ср. ответ теххоуча.

Bram Vanroy 14.08.2018 19:57
Ответ принят как подходящий

Ваши проблемы связаны: а именно, цепочка обещаний должна быть returned. Если вы не выполняете return Promise, вы отключаете любую обработку Promise#catch вызывающего абонента, и любые ошибки в вашем коде Promise / then приведут к необработанным ошибкам отклонения обещания, например тем, что вы получили:

Uncaught (in promise) Error when getting venue details

Это неперехваченное отклонение обещания появляется в вашем коде, который обрабатывает разрешение fetch:

if (response.ok) {
  console.info('ok')
  return response.json()
} else {
  Promise.reject('Error when getting venue details')  // <----
}

Поскольку этот код используется для создания вашего массива venuePromises, его значение return будет заполнять venuePromises. Если ответ был удовлетворительным, этот элемент массива будет иметь ответ JSON от return response.json(). Если ответ не прошел, значит, оператор return не выполняется, поэтому элемент массива имеет значение undefined. Таким образом, venuePromises будет выглядеть так:

[
  { /** some object for successful response */ },
  undefined,
  { /** some other object */ },
  ...
]

Таким образом, когда к этому массиву обращается обработчик успеха вашего Promise.all, вы получаете ошибку TypeError, поскольку вы ожидали, что все элементы venuePromises будут действительными. Эта ошибка TypeError перехватывается обработчиком Promise.all.catch (поэтому она регистрируется, и вы получаете текст «резервной копии» в своем журнале).

Чтобы исправить это, вам необходимо установить return в Promise.reject, а также в Promise.all. Обратите внимание, что есть некоторые случаи неявный return, но я считаю, что лучше быть явным, особенно если оператор занимает несколько строк. Поскольку вы возвращаете оператор Promise.all, вы можете передать его .then и .catch вызывающей стороне, что приведет к уменьшению на один уровень вложенности и на один дублированный обработчик .catch.

fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
    .then(response => response.json())
    .then(jsonData => {
        const venueIds = jsonData.response.venues.map(venue => venue.id);
        const venuePromises = venueIds.map(venueId => {
            let link = `https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`;
            return fetch(link).then(response => {
                // Must check for response.ok, because catch() does not catch 429
                if (response.ok) {
                    console.info('ok');
                    return response.json();
                } else {
                    console.info(`FAILED: ${link}`);
                    // Return a Promise
                    return Promise.reject(`Error when getting venue details for '${venueId}'`);
                }
            });
        });

        return Promise.all(venuePromises);
    })
    .then(venueData => {
        const venues = venueData.map(entry => entry.response.venue);
        this.parseFsqData(venues);
    })
    .catch(e => {console.info(e); getBackupData()});

function getBackupData() {
    console.info('backup')
}

Ах, в этом есть смысл. Я ценю подробное объяснение. После применения ваших исправлений код действительно работает должным образом. Я думаю. На данный момент API возвращает ошибку 429 (что я не возражаю), и мой код возвращается к данным резервного копирования. Однако консоль Chrome по-прежнему показывает все неудачные запросы GET (ошибка 429). Означает ли это, что мне все еще не хватает некоторых ошибок, которые я не уловил, или это какое-то поведение браузера, которое невозможно обойти при обнаружении неудачных запросов GET?

Bram Vanroy 14.08.2018 19:56

Я не знаком с консолью Chrome, поэтому не могу вам помочь. Вероятно, вам также следует передать данные о месте проведения или данные резервной копии в this.parseFsqData (или что-то еще), то есть .then(venueData => venueData.map(entry => entry.response.venue)).catch(e => { console.info(e); return getBackupData()}).then(venues => { this.parseFsqData(venues); });

tehhowch 14.08.2018 19:58

Я считаю, что решение довольно простое; В ответе вложенного метода выборки отсутствует оператор возврата. Вы должны избавиться от этой загадочной ошибки, как только она будет исправлена.

const venuePromises = venueIds.map(venueId => {
    <missing return statement here> fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
      .then(response => {

Другие вопросы по теме