only - javascript promise example



Função assíncrona não retornando valor, mas console.log() faz: como fazer? (2)

Quanto ao seu comentário; Vou adicioná-lo como resposta.

O código que você escreve em JavaScript é executado em um thread, o que significa que, se o seu código realmente pudesse esperar por algo, ele bloquearia qualquer outro código de ser executado. O loop de eventos do JavaScript é explicado muito bem neste vídeo e se você gosta de ler nesta página .

Um bom exemplo de código de bloqueio no navegador é alert("cannot do anything until you click ok"); . Alerta bloqueia tudo, o usuário não pode nem mesmo rolar ou clicar em nada na página e seu código também bloqueia de executar.

Promise.resolve(22)
.then(x=>alert("blocking")||"Hello World")
.then(
  x=>console.log(
    "does not resolve untill you click ok on the alert:",
    x
  )
);

Execute isso em um console e você verá o que quero dizer com bloqueio.

Isso cria um problema quando você quer fazer algo que leva tempo. Em outras estruturas, você usaria um thread ou processos, mas não existe isso em JavaScript (tecnicamente, existe com web worker e fork no nó, mas isso é outra história e geralmente muito mais complicado do que usar as APIs assíncronas).

Então, quando você quer fazer uma requisição http, você pode usar a fetch mas a busca leva algum tempo para terminar e a sua função não deve bloquear (é preciso retornar algo o mais rápido possível). É por isso que o fetch retorna uma promessa.

Observe que a busca é implementada pelo navegador / node e é executada em outro thread, somente o código que você escreve é ​​executado em um thread, portanto, iniciar muitas promessas que só executam o código que você escreve não acelerará nada, mas chamará as APIs nativas assíncronas em paralelo.

Antes promete que o código assíncrono usaria retornos de chamada ou retornaria um objeto observável (como XmlHttpRequest), mas vamos cobrir promessas, já que você pode converter o código mais tradicional em uma promessa de qualquer maneira.

Uma promessa é um objeto que tem uma função then (e um monte de coisas que é açúcar para então, mas faz o mesmo), esta função leva 2 parâmetros.

  1. Resolver o manipulador: Uma função que será chamada pela promessa quando a promessa for resolvida (não tem erros e está concluída). A função será passada em um argumento com o valor de resolução (para solicitações http, geralmente, é a resposta).
  2. Rejeitar manipulador: Uma função que será chamada pela promessa quando a promessa for rejeitada (tem um erro). Esta função será passada em um argumento, geralmente é o erro ou motivo da rejeição (pode ser uma string, número ou qualquer coisa).

Convertendo retorno de chamada para promessa.

As api tradicionais (especialmente as api's do nodejs) usam callbacks:

traditionalApi(
  arg
  ,function callback(err,value){ 
    err ? handleFail(err) : processValue(value);
  }
);

Isso dificulta que o programador capture erros ou manipule o valor de retorno de maneira linear (de cima para baixo). Fica ainda mais impossível tentar fazer coisas paralelas ou estranguladas paralelamente ao tratamento de erros (impossível de ler).

Você pode converter api tradicional para promessas com new Promise

const apiAsPromise = arg =>
  new Promise(
    (resolve,reject)=>
      traditionalApi(
        arg,
        (err,val) => (err) ? reject(err) : resolve(val)
      )
  )

async aguardam

Isso é o que é chamado de açúcar de sintaxe para promessas. Torna as funções de consumo promissoras mais tradicionais e mais fáceis de ler. Isto é, se você gosta de escrever código tradicional, eu diria que compor pequenas funções é muito mais fácil de ler. Por exemplo, você consegue adivinhar o que isso faz ?:

const handleSearch = search =>
  compose([
    showLoading,
    makeSearchRequest,
    processRespose,
    hideLoading
  ])(search)
  .then(
    undefined,//don't care about the resolve
    compose([
      showError,
      hideLoading
    ])
  );

Anayway; ranting suficiente. A parte importante é entender que o async await não inicia de fato outro thread, as funções async sempre retornam uma promessa e a await não bloqueia ou espera. É o açúcar de sintaxe para someFn().then(result=>...,error=>...) e se parece com:

async someMethod = () =>
  //syntax sugar for:
  //return someFn().then(result=>...,error=>...)
  try{
    const result = await someFn();
    ...
   }catch(error){
     ...
   }
}

Os exemplos sempre mostram try catch mas você não precisa fazer isso, por exemplo:

var alwaysReject = async () => { throw "Always returns rejected promise"; };
alwaysReject()
.then(
  x=>console.log("never happens, doesn't resolve")
  ,err=>console.warn("got rejected:",err)
);

Qualquer erro lançado ou await retorno de uma promessa rejeitada fará com que a função assíncrona retorne uma promessa rejeitada (a menos que você tente capturá-la). Muitas vezes, é desejável apenas deixá-lo falhar e ter os erros de identificador do chamador.

Erros de captura podem ser necessários quando você deseja que a promessa seja bem-sucedida com um valor especial para promessas rejeitadas, para que você possa lidar com isso mais tarde, mas a promessa não é tecnicamente rejeitada, portanto, sempre será resolvida.

Um exemplo é o Promise.all , isso exige uma série de promessas e retorna uma nova promessa que resolve uma matriz de valores resolvidos ou rejeita quando qualquer um deles rejeita . Você pode apenas querer obter os resultados de todas as promessas de volta e filtrar os rejeitados:

const Fail = function(details){this.details=details;},
isFail = item => (item && item.constructor)===Fail;
Promise.all(
  urls.map(//map array of urls to array of promises that don't reject
    url =>
      fetch(url)
      .then(
        undefined,//do not handle resolve yet
        //when you handle the reject this ".then" will return
        //  a promise that RESOLVES to the value returned below (new Fail([url,err]))
        err=>new Fail([url,err])
      )
  )
)
.then(
  responses => {
    console.log("failed requests:");
    console.log(
      responses.filter(//only Fail type
        isFail
      )
    );
    console.log("resolved requests:");
    console.log(
      responses.filter(//anything not Fail type
        response=>!isFail(response)
      )
    );
  }
);

https://ffff65535.com

Esta questão já tem uma resposta aqui:

Eu tenho uma classe es6, com um método init() responsável por buscar dados, transformá-los e atualizar a propriedade da classe this.data com dados recém-transformados. Por enquanto, tudo bem. A classe em si tem outro método getPostById() , para fazer o que parece. Aqui está o código para a classe:

class Posts {
  constructor(url) {
    this.ready = false
    this.data = {}
    this.url = url
  }
  async init() {
      try { 
        let res = await fetch( this.url )
        if (res.ok) {
            let data = await res.json()

          // Do bunch of transformation stuff here

          this.data = data
          this.ready = true
            return data
        }
      } 
      catch (e) { 
         console.log(e)
      }
  }
  getPostById(id){
     return this.data.find( p => p.id === id )
  }
}  

Direto, exceto que eu tenho um mecanismo async/await no método init() . Agora, esse código funcionará corretamente:

let allPosts = new Posts('https://jsonplaceholder.typicode.com/posts')

allPosts.init()
        .then( d => console.log(allPosts.getPostById(4)) )
// resulting Object correctly logged in console

mas só é impresso no console: como eu poderia usar allPosts.getPostById(4) como um return de uma função?

Gostar:

let myFunc = async () => {
   const postId = 4
   await allPosts.init()  // I need to wait for this to finish before returning

   // This is logging correct value
   console.log( 'logging: ' + JSON.stringify(allPosts.getPostById( postId ), null, 4) )

   // How can I return the RESULT of allPosts.getPostById( postId ) ???
   return allPosts.getPostById( postId )
}

myFunc() retorna um Promise mas não o valor final. Eu li vários posts relacionados sobre o assunto, mas todos eles dão exemplos de registro, nunca retornando.

Aqui está um violino que inclui duas maneiras de lidar com o init() : usando o Promise e usando async/await . Não importa o que eu tente, não consigo usar o VALOR FINAL de getPostById(id) .

A questão deste post é: como posso criar uma função que RETURN o valor de getPostById(id) ?

EDITAR:

Muitas boas respostas tentando explicar o que as Promessas são em relação ao loop de execução principal. Depois de muitos vídeos e outras boas leituras, aqui está o que eu entendo agora:

minha função init() retorna corretamente. No entanto, dentro do loop principal do evento: ele retorna um Promise , então é meu trabalho capturar o resultado dessa promessa dentro de um loop paralelo (não um novo thread real). Para capturar o resultado do loop paralelo, existem duas maneiras:

  1. use .then( value => doSomethingWithMy(value) )

  2. use let value = await myAsyncFn() . Agora aqui está o soluço tolo:

await só pode ser usado dentro de uma função async : p

assim retornando um Promise, utilizável com o await que deveria ser embutido em uma função async , que será utilizável com o await etc ...

Isto significa que não podemos realmente esperar por uma Promessa: em vez disso, devemos capturar o loop paralelo indefinidamente: usando .then() ou async/await .

Obrigado pela ajuda !


Sua pergunta e os comentários sugerem que você poderia usar um pequeno empurrão de intuição sobre o funcionamento do evento. É realmente confuso no começo, mas depois de um tempo se torna uma segunda natureza.

Em vez de pensar sobre o VALOR FINAL, pense no fato de que você tem um único encadeamento e não pode pará-lo - portanto, você quer o FUTURE VALUE - o valor no próximo ou em algum futuro loop de eventos. Tudo o que você escreve que não é assíncrono vai acontecer quase que imediatamente - as funções retornam com algum valor ou são indefinidas imediatamente . Não há nada que você possa fazer. Quando você precisa de algo de forma assíncrona, é necessário configurar um sistema que esteja pronto para lidar com os valores assíncronos quando eles retornarem em algum momento no futuro. Isto é o que todos os eventos, callbacks, promessas (e async / await) tentam ajudar. Se alguns dados forem assíncronos, você simplesmente não poderá usá-los no mesmo loop de eventos.

Então, o que você faz?

Se você quer um padrão onde você cria uma instância, chame init() e, em seguida, alguma função que processe ainda mais, basta configurar um sistema que faça o processamento quando os dados chegarem. Existem muitas maneiras de fazer isso. Aqui está uma maneira que é uma variação da sua classe:

function someAsync() {
  console.log("someAsync called")
  return new Promise(resolve => {
    setTimeout(() => resolve(Math.random()), 1000)
  })
}

class Posts {
  constructor(url) {
    this.ready = false
    this.data = "uninitilized"
    this.url = url
  }
  init() {
    this.data = someAsync()

  }
  time100() {
    // it's important to return the promise here
    return this.data.then(d => d * 100)
  }
}

let p = new Posts()
p.init()
processData(p)
// called twice to illustrate point
processData(p)

async function processData(posts) {
  let p = await posts.time100()
  console.log("randomin * 100:", p)
}

init() salva a promessa retornada de someAsync() . someAsync() pode ser qualquer coisa que retorne uma promessa. Ele salva a promessa em uma propriedade de instância. Agora você pode chamar then() ou usar async / wait para obter o valor. Ele retornará imediatamente o valor se a promessa já tiver sido resolvida ou lidará com ela quando tiver sido resolvida. Eu chamei processData(p) duas vezes apenas para ilustrar que ele não someAsync() o someAsync() duas vezes.

Isso é apenas um padrão. Há muito mais - usando eventos, observáveis, usando apenas then() diretamente, ou mesmo callbacks que estão fora de moda, mas ainda podem ser úteis.





async-await