Dlaczego .json () zwraca obietnicę?

116

fetch()Ostatnio bawiłem się z interfejsem API i zauważyłem coś, co było nieco dziwaczne.

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.datazwraca Promiseobiekt. http://jsbin.com/wofulo/2/edit?js,output

Jednak jeśli jest napisane jako:

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

let iterator = fetch(url);

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

posttutaj jest standard, do Objectktórego można uzyskać dostęp do atrybutu tytułu. http://jsbin.com/wofulo/edit?js,output

Moje pytanie brzmi: dlaczego response.jsonzwraca obietnicę w literale obiektu, ale zwraca wartość, jeśli została zwrócona?

haveacigaro
źródło
1
Ma to sens, jeśli weźmiesz pod uwagę, że response.json()obietnica może zostać odrzucona, jeśli odpowiedź nie jest poprawnym formatem JSON.
ssube
1
Wartość jest zwracana, ponieważ obietnica została rozwiązana, przekazując wartość w response.json (). Teraz wartość jest dostępna w metodzie then.
Jose Hermosilla Rodrigo

Odpowiedzi:

168

Dlaczego response.jsonzwraca obietnicę?

Ponieważ otrzymujesz, responsegdy tylko nadejdą wszystkie nagłówki. Wywołanie .json()daje kolejną obietnicę dotyczącą treści odpowiedzi http, która nie została jeszcze załadowana. Zobacz także Dlaczego obiekt odpowiedzi z JavaScript Fetch API jest obietnicą? .

Dlaczego otrzymam wartość, jeśli zwrócę obietnicę od thenobsługi?

Ponieważ tak działają obietnice . Możliwość zwracania obietnic z wywołania zwrotnego i przyjmowania ich jest ich najważniejszą funkcją, dzięki czemu można je łączyć w łańcuchy bez zagnieżdżania.

Możesz użyć

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

lub jakakolwiek inna metoda dostępu do poprzedniej obietnicy skutkuje utworzeniem łańcucha .then (), aby uzyskać status odpowiedzi po oczekiwaniu na treść json.

Bergi
źródło
Wydaje się dziwne, że nie mogę po prostu czekać, aż dane wrócą, używając Promise, a kiedy nadejdą, przekonwertować je na json? A może w takim przypadku mógłbym po prostu użyć JSON.parse()zamiast res.json()??
Kokodoko
8
@Kokodoko res.json()to w zasadzie skrót do res.text().then(JSON.parse). Obie czekają na dane przy użyciu obietnicy i analizują plik json.
Bergi
@Bergi, cześć, przepraszam, napotkałem pewne zamieszanie, to znaczy używając następnie (res => res.json ()) wysyłamy kolejne żądanie pobrania JSON?
mirzhal
1
@mirzhal Nie, nie ma innej prośby. Po prostu odczytuje (asynchronicznie!) Resztę odpowiedzi.
Bergi
14

Ta różnica wynika bardziej niż fetch()konkretnie z zachowania Obietnic .

Kiedy .then()callback zwraca dodatkowy Promise, następny .then()callback w łańcuchu jest zasadniczo powiązany z tą obietnicą, otrzymując rozwiązanie lub odrzucenie spełnienia i wartości.

Drugi fragment można również zapisać jako:

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

Zarówno w tej formie, jak i w Twojej, wartość postokreśla przyrzeczenie zwrócone z response.json().


Kiedy jednak zwracasz zwykły Object, .then()uważa, że ​​wynik jest pomyślny i rozwiązuje się natychmiast, podobnie do:

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

postw tym przypadku jest po prostu tym, Objectco stworzyłeś, który posiada Promisew swojej datawłasności. Oczekiwanie na wypełnienie się tej obietnicy jest wciąż niepełne.

Jonathan Lonowski
źródło
7

Ponadto, to, co pomogło mi zrozumieć ten konkretny scenariusz, który opisałeś, to dokumentacja Promise API , w szczególności gdzie wyjaśnia, w jaki sposób obietnica zwrócona przez thenmetodę zostanie rozwiązana w różny sposób w zależności od tego, co zwraca program obsługi fn :

jeśli funkcja obsługi:

  • zwraca wartość, a obietnica zwrócona następnie zostaje rozwiązana z wartością zwracaną jako wartością;
  • zgłasza błąd, zwrócona obietnica zostaje odrzucona z wyrzuconym błędem jako wartością;
  • zwraca już rozwiązaną obietnicę, obietnica zwrócona do tego czasu zostaje rozwiązana z wartością tej obietnicy jako jej wartością;
  • zwraca już odrzuconą obietnicę, obietnica zwrócona do tego czasu zostaje odrzucona z wartością tej obietnicy jako wartością.
  • zwraca inny przedmiot obietnicy oczekującej, rozstrzygnięcie / odrzucenie promesy zwróconej do tego czasu nastąpi po rozwiązaniu / odrzuceniu promesy zwróconej przez opiekuna. Ponadto wartość promesy zwróconej do tego czasu będzie taka sama jak wartość promesy zwróconej przez handlowca.
Gera Zenobi
źródło
5

Oprócz powyższych odpowiedzi, oto, w jaki sposób możesz obsłużyć odpowiedź serii 500 z interfejsu API, w której pojawia się komunikat o błędzie zakodowany w formacie 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
źródło