Por que .json () retorna uma promessa?

116

Eu tenho mexido com a fetch()API recentemente e percebi algo um pouco estranho.

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

No entanto, se estiver escrito como:

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

let iterator = fetch(url);

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

postaqui está um padrão Objectque você pode acessar o atributo title. http://jsbin.com/wofulo/edit?js,output

Portanto, minha pergunta é: por que response.jsonretorna uma promessa em um objeto literal, mas retorna o valor se acabou de retornar?

Haveacigaro
fonte
1
Isso faz sentido quando você considera que a response.json()promessa pode ser rejeitada se a resposta não for um JSON válido.
ssube
1
O valor é retornado porque a promessa foi resolvida passando o valor em response.json (). Agora o valor está disponível no método then.
Jose Hermosilla Rodrigo

Respostas:

168

Por que response.jsonretorna uma promessa?

Porque você recebe o responseassim que todos os cabeçalhos chegarem. Chamar .json()oferece outra promessa para o corpo da resposta http que ainda não foi carregada. Consulte também Por que o objeto de resposta da API de busca de JavaScript é uma promessa? .

Por que obtenho o valor se retornar a promessa do thenmanipulador?

Porque é assim que as promessas funcionam . A capacidade de retornar promessas do retorno de chamada e fazer com que sejam adotadas é seu recurso mais relevante, pois as torna encadeadas sem aninhamento.

Você pode usar

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

ou qualquer outra das abordagens para acessar resultados de promessa anteriores em uma cadeia .then () para obter o status de resposta após ter esperado o corpo json.

Bergi
fonte
Parece estranho que eu não posso simplesmente esperar os dados retornarem usando uma Promise, e quando eles chegarem convertê-los para json? Ou talvez, nesse caso, eu pudesse apenas usar em JSON.parse()vez de res.json()??
Kokodoko
8
@Kokodoko res.json()é basicamente um atalho para res.text().then(JSON.parse). Ambos aguardam os dados usando uma promessa e analisam o json.
Bergi
@Bergi, oi, desculpe me deparei com alguma confusão, ou seja, usando então (res => res.json ()) enviamos outra requisição para obter JSON?
mirzhal
1
@mirzhal Não, não há outro pedido. Ele apenas lê (de forma assíncrona!) O resto da resposta.
Bergi
14

Essa diferença se deve ao comportamento das Promessas mais do que fetch()especificamente.

Quando um .then()retorno de chamada retorna um adicional Promise, o próximo .then()retorno de chamada na cadeia é essencialmente vinculado a essa promessa, recebendo sua resolução ou rejeição, cumprimento e valor.

O segundo snippet também pode ter sido escrito como:

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

Tanto neste formulário como no seu, o valor de posté fornecido pela promessa devolvida de response.json().


Quando você retorna um plano Object, no entanto, .then()considera que um resultado bem-sucedido e se resolve imediatamente, semelhante a:

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

postneste caso, é simplesmente o que Objectvocê criou, que contém um Promiseem sua datapropriedade. A espera para que essa promessa seja cumprida ainda está incompleta.

Jonathan Lonowski
fonte
7

Além disso, o que me ajudou a entender esse cenário específico que você descreveu é a documentação da API Promise , especificamente onde ela explica como a promessa retornada pelo thenmétodo será resolvida de forma diferente dependendo do que o manipulador fn retorna:

se a função de manipulador:

  • retorna um valor, a promessa retornada até então é resolvida com o valor retornado como seu valor;
  • lança um erro, a promessa retornada por then é rejeitada com o erro lançado como seu valor;
  • retorna uma promessa já resolvida, a promessa retornada até então é resolvida com o valor dessa promessa como seu valor;
  • retornar uma promessa já rejeitada, a promessa retornada até então será rejeitada com o valor dessa promessa como seu valor.
  • retorna outro objeto de promessa pendente, a resolução / rejeição da promessa retornada até então será subsequente à resolução / rejeição da promessa retornada pelo manipulador. Além disso, o valor da promessa retornada até então será igual ao valor da promessa retornada pelo manipulador.
Gera Zenobi
fonte
5

Além das respostas acima, aqui está como você pode lidar com uma resposta da série 500 de sua api, em que recebe uma mensagem de erro codificada em 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
fonte