Почему .json () возвращает обещание?

117

fetch()Недавно я возился с api и заметил кое-что, что было немного странно.

let url = "http://jsonplaceholder.typicode.com/posts/6";

let iterator = fetch(url);

iterator
  .then(response => {
      return {
          data: response.json(),
          status: response.status
      }
  })
  .then(post => document.write(post.data));
;

post.dataвозвращает Promiseобъект. http://jsbin.com/wofulo/2/edit?js,output

Однако, если это написано как:

let url = "http://jsonplaceholder.typicode.com/posts/6";

let iterator = fetch(url);

iterator
  .then(response => response.json())
  .then(post => document.write(post.title));
;

postвот стандарт, к Objectкоторому вы можете получить доступ к атрибуту title. http://jsbin.com/wofulo/edit?js,output

Итак, мой вопрос: почему response.jsonвозвращает обещание в литерале объекта, но возвращает значение, если оно было только что возвращено?

Хаачигаро
источник
1
Это имеет смысл, если вы считаете, что response.json()обещание может быть отклонено, если ответ недействителен JSON.
ssube
1
Значение возвращается, потому что обещание было разрешено с передачей значения в response.json (). Теперь значение доступно в методе then.
Хосе Эрмосилья Родриго

Ответы:

168

Почему response.jsonвозвращается обещание?

Потому что вы получите, responseкак только будут получены все заголовки. Вызов .json()дает вам еще одно обещание для тела HTTP-ответа, которое еще предстоит загрузить. См. Также Почему объект ответа из JavaScript fetch API является обещанием? .

Почему я получаю значение, если возвращаю обещание от thenобработчика?

Потому что так работают обещания . Возможность возвращать обещания из обратного вызова и принимать их - их самая важная функция, она делает их цепочкой без вложенности.

Ты можешь использовать

fetch(url).then(response => 
    response.json().then(data => ({
        data: data,
        status: response.status
    })
).then(res => {
    console.log(res.status, res.data.title)
}));

или любой другой из подходов к доступу к предыдущему обещанию приводит к цепочке .then () для получения статуса ответа после ожидания тела json.

Берги
источник
Кажется странным, что я не могу просто дождаться возврата данных с помощью Promise, а когда они появятся, преобразовать их в json? Или, может быть, в этом случае я мог бы просто использовать JSON.parse()вместо res.json()??
Kokodoko
8
@Kokodoko res.json()- это ярлык для res.text().then(JSON.parse). Оба ждут данных, используя обещание, и анализируют json.
Берги
@Bergi, привет, извините, я столкнулся с некоторой путаницей, то есть, используя then (res => res.json ()), мы отправляем еще один запрос на получение JSON?
mirzhal
1
@mirzhal Нет, другого запроса нет. Он просто читает (асинхронно!) Остальную часть ответа.
Берги
14

Это различие связано с поведением промисов более чем fetch()конкретно.

Когда .then()обратный вызов возвращает дополнительный Promise, следующий .then()обратный вызов в цепочке по существу привязан к этому обещанию, получая его разрешение или отклонение выполнения и значение.

Второй фрагмент также можно было записать как:

iterator.then(response =>
    response.json().then(post => document.write(post.title))
);

И в этой, и в вашей форме значение postпредоставляется обещанием, возвращаемым из response.json().


Однако, когда вы возвращаете равнину Object, он .then()считает, что результат успешный, и немедленно разрешается, аналогично:

iterator.then(response =>
    Promise.resolve({
      data: response.json(),
      status: response.status
    })
    .then(post => document.write(post.data))
);

postв данном случае это просто Objectсозданный вами объект , который содержит Promiseв своем dataсвойстве. Ожидание выполнения этого обещания еще не завершено.

Джонатан Лоновски
источник
7

Кроме того, что помогло мне понять этот конкретный сценарий, который вы описали, так это документация API Promise , в частности, где объясняется, как обещанное, возвращаемое thenметодом, будет разрешаться по-разному в зависимости от того, что возвращает обработчик fn :

если функция-обработчик:

  • возвращает значение, обещание, возвращенное к тому времени, разрешается с возвращенным значением в качестве его значения;
  • выдает ошибку, обещание, возвращенное к тому времени, отклоняется с выданной ошибкой в ​​качестве его значения;
  • возвращает уже решенное обещание, обещание, возвращенное к тому времени, разрешается со значением этого обещания в качестве его значения;
  • возвращает уже отклоненное обещание, обещание, возвращенное к тому времени, отклоняется со значением этого обещания в качестве его значения.
  • возвращает другой ожидающий объект обещания, разрешение / отклонение обещания, возвращенного к тому времени, будет последующим после разрешения / отклонения обещания, возвращенного обработчиком. Кроме того, значение обещания, возвращаемого к этому моменту, будет таким же, как значение обещания, возвращенного обработчиком.
Гера Зеноби
источник
5

В дополнение к приведенным выше ответам вот как вы можете обработать ответ серии 500 от вашего api, где вы получаете сообщение об ошибке, закодированное в json:

function callApi(url) {
  return fetch(url)
    .then(response => {
      if (response.ok) {
        return response.json().then(response => ({ response }));
      }

      return response.json().then(error => ({ error }));
    })
  ;
}

let url = 'http://jsonplaceholder.typicode.com/posts/6';

const { response, error } = callApi(url);
if (response) {
  // handle json decoded response
} else {
  // handle json decoded 500 series response
}
jcroll
источник