Esta tradução está incompleta. Por favor, ajude a traduzir este artigo.

Uma Promise é um objeto que representa a eventual conclusão ou falha de uma operação assincrona. Como a maioria das pessoas consomem promisses já criadas, este guia explicará o consumo de promisses devolvidas antes de explicar como criá-las.

Essencialmente, uma promise é um objeto retornado para o qual você adiciona callbacks, em vez de passar callbacks para uma função.

Por exemplo, em vez de uma função old-style que espera dois callbacks, e chama um deles em uma eventual conclusão ou falha:

function successCallback(result) {
  console.log("It succeeded with " + result);
}

function failureCallback(error) {
  console.log("It failed with " + error);
}

doSomething(successCallback, failureCallback);

…funções modernas retornam uma promisse e então você pode adicionar seus callbacks:

const promise = doSomething(); 
promise.then(successCallback, failureCallback);

…ou simplesmente:

doSomething().then(successCallback, failureCallback);

Nós chamamos isso de chamada de função assincrona. Essa convenção tem várias vantagens. Vamos explorar cada uma delas.

Garantias

Ao contrário dos callbacks com retornos de funções old-style, uma promisse vem com algumas garantias:

  • Callbacks nunca serão chamados antes da conclusão da execução atual do loop de eventos do JavaScript. 
  • Callbacks adicionadas com .then mesmo depois do sucesso ou falha da operação assincrona, serão chamadas, como acima.
  • Multiplos callbacks podem ser adicionados chamando-se .then várias vezes, para serem executados independentemente da ordem de inserção.

Mas o benefício mais imediato da promises é o encadeamento.

 

Encadeamento

Uma necessidade comum é executar duas ou mais operações assincronas consecutivas, onde cada operação subsequente começa quando a operação anterior é bem sucedida, com o resultado do passo anterior. Nós conseguimos isso criando uma cadeia de promises.

Aqui está a magica: a função then retorna uma nova promise, diferente da original:

const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

or

const promise2 = doSomething().then(successCallback, failureCallback);

Essa segunda promise representa a conclusão não apenas de doSomething(), mas também do successCallback ou failureCallback que você passou, que podem ser outras funções assincronas que retornam uma promise. Quando esse for o caso, quaisquer callbacks adicionados a promise2 serão enfileiradas atrás da promise retornada por successCallback ou failureCallback.

Basicamente, cada promise representa a completude de outro passo assíncrono na cadeia.

Antigamente, realizar operações assíncronas comuns em uma linha levaria à clássica pirâmide da desgraça :

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

Ao invés disso, com funções modernas, nós atribuímos nossas callbacks às promises retornadas, formando uma cadeia de promise:

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

Os argumentos para then são opcionais, e catch(failureCallback) é uma abreviação para then(null, failureCallback). Você pode também pode ver isso escrito com arrow functions:

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

Importante: Sempre retorne um resultado, de outra forma as callbacks não vão capturar o resultado da promise anterior.

Encadeando depois de um catch

É possivel encadear depois de uma falha, i.e um catch, isso é muito útil para realizar novas ações mesmo depois de uma falha no encadeamento. Leia o seguinte exemplo: 

new Promise((resolve, reject) => {
    console.log('Initial');

    resolve();
})
.then(() => {
    throw new Error('Something failed');
        
    console.log('Do this');
})
.catch(() => {
    console.log('Do that');
})
.then(() => {
    console.log('Do this whatever happened before');
});

Isso vai produzir o seguinte texto:

Initial
Do that
Do this whatever happened before

Observe que o texto "Do this" não foi impresso por conta que o erro "Something failed" causou uma rejeição.

Propagação de erros

You might recall seeing failureCallback three times in the pyramid of doom earlier, compared to only once at the end of the promise chain:

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);

Basically, a promise chain stops if there's an exception, looking down the chain for catch handlers instead. This is very much modeled after how synchronous code works:

try {
  const result = syncDoSomething();
  const newResult = syncDoSomethingElse(result);
  const finalResult = syncDoThirdThing(newResult);
  console.log(`Got the final result: ${finalResult}`);
} catch(error) {
  failureCallback(error);
}

This symmetry with synchronous code culminates in the async/await syntactic sugar in ECMAScript 2017:

async function foo() {
  try {
    const result = await doSomething();
    const newResult = await doSomethingElse(result);
    const finalResult = await doThirdThing(newResult);
    console.log(`Got the final result: ${finalResult}`);
  } catch(error) {
    failureCallback(error);
  }
}

It builds on promises, e.g. doSomething() is the same function as before. You can read more about the syntax here.

Promises solve a fundamental flaw with the callback pyramid of doom, by catching all errors, even thrown exceptions and programming errors. This is essential for functional composition of asynchronous operations.

Creating a Promise around an old callback API

A Promise can be created from scratch using its constructor. This should be needed only to wrap old APIs.

In an ideal world, all asynchronous functions would already return promises. Alas, some APIs still expect success and/or failure callbacks to be passed in the old way. The quintessential example is the setTimeout() function:

setTimeout(() => saySomething("10 seconds passed"), 10000);

Mixing old-style callbacks and promises is problematic. If saySomething fails or contains a programming error, nothing catches it.

Luckily we can wrap it in a promise. Best practice is to wrap problematic functions at the lowest possible level, and then never call them directly again:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);

Basically, the promise constructor takes an executor function that lets us resolve or reject a promise manually. Since setTimeout doesn't really fail, we left out reject in this case.

Composition

Promise.resolve() and Promise.reject() are shortcuts to manually create an already resolved or rejected promise respectively. This can be useful at times.

Promise.all() and Promise.race() are two composition tools for running asynchronous operations in parallel.

Sequential composition is possible using some clever JavaScript:

[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());

Basically, we reduce an array of asynchronous functions down to a promise chain equivalent to: Promise.resolve().then(func1).then(func2);

This can also be done with a reusable compose function, which is common in functional programming:

const applyAsync = (acc,val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));

The composeAsync function will accept any number of functions as arguments, and will return a new function that accepts an initial value to be passed through the composition pipeline. This is beneficial because any or all of the functions may be either asynchronous or synchronous, and they are guaranteed to be executed in the correct order:

const transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2);
transformData(data);

In ECMAScript 2017, sequential composition can be done more simply with async/await:

for (const f of [func1, func2]) {
  await f();
}

Timing

To avoid surprise, functions passed to then will never be called synchronously, even with an already-resolved promise:

Promise.resolve().then(() => console.log(2));
console.log(1); // 1, 2

Instead of running immediately, the passed-in function is put on a microtask queue, which means it runs later when the queue is emptied at the end of the current run of the JavaScript event loop, i.e. pretty soon:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

wait().then(() => console.log(4));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3));
console.log(1); // 1, 2, 3, 4

See also

Etiquetas do documento e colaboradores

Colaboradores desta página: Jeovano, Tiagosb
Última atualização por: Jeovano,