What are Javascript Promises (promises)?

Asked

Viewed 9,745 times

43

  • 5

    I don’t know if the tag should be promises or Promises :)

  • 9

    Because it’s common terminology in this context, I prefer Promises in the tag of what promises.

  • 1

    Tip: leaves the tag as promises (since this is the OSPt) and makes Promises be his synonym.

  • Imagine that Promises are steroid callbacks. Then read the texts.

  • 3

    On the day it is Javaescrita, we can speak of Promessas: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

3 answers

41


Promises of javascript is an implementation of the Futures idea of functional programming.

It has become the standard way of working with asynchronous javascript code.

An object preserves the promise that the function it generated will sometime in the future end and return you a response. It can be a positive or negative response. The Promise can be passed to other functions or returned.

There are several implementations of Promise for javascript today. They all end up following this specification.

The advantages of using Names and not callbacks is that you avoid callback Hells and callback cascades that make code hard to read and understand.

EDIT: I have put a more pertinent examples that show the potential of events when you have several callback levels.

Ex with callback.

function isUserTooYoung(id, callback) {
    openDatabase(function(db) {
        getCollection(db, 'users', function(col) {
            find(col, {'id': id},function(result) {
                result.filter(function(user) {
                    callback(user.age < cutoffAge)
                })
            })
        })
    })
}

The nesting level can become much higher than this....

with Promises would be like this:

function isUserTooYoung(id) {
    return openDatabase(db)
        .then(getCollection)
        .then(find.bind(null, {'id': id}))
        .then(function(user) {
            return user.age < cutoffAge;
        });
}

Each function does just what it should do and does not need to worry about calling callback or knowing what the function it called is waiting for.

  • @Guilherme Lautert , How can I improve my answer so that it is accepted? What details do you think were left out?

23

Promises (or Promises) are an abstraction used to represent action flows in asynchronous code, and in Javascript Promise is an object that represents the result of an asynchronous operation, this result can be successful (generated by the function call resolve and triggers the call of the method then) or failed (generated by function call reject and triggers the call of the method catch). A simple example to illustrate the concept:

// Aqui criamos uma Promesa que será "resolvida" após o disparo do timer
// (1 segundo)
var p = new Promise(function(resolve, reject) {
  setTimeout(function() {
    $('body').append('Timer executado<br>');
    resolve();
  }, 1000);
});

// a função anônima passada como parâmetro para ".then" representa a ação que
// será realizada quando nossa promessa for resolvida/cumprida
p.then(function() {
  $('body').append('Promise resolvida<br>');
});

$('body').append('Texto qualquer<br><br>');
<script src="https://cdn.jsdelivr.net/es6-promise/3.1.2/es6-promise.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

In this example setTimeout is an asynchronous operation that is transformed into a Promise and assigned the variable p. So we have the variable p which represents the promise of some operation that will eventually occur, and in this case it occurs after the time stipulated in the timer, 1 second / 1000 milliseconds, at which time the string is append "'Timer executado<br>'" and Promise is finished|complete|solved by calling the function resolve, thus triggering the method then executing the append function of the string "Promise resolvida".

As for the usefulness of Promises, it lies in its ability to represent asynchronous flow in a way that is understandable to the developer. As an example see how is the "traditional/old" mode to represent such a flow in javascript:

algumaFnAsync(function() {
    outraAsyncFn(function() {
        maisUmaAsyncFn(function() {
           // código que faz algo após todas
           // as operações assíncronas terem completado
        });
    });
});

You have asynchronous functions that receive an anonymous function as a parameter, and this function will be executed when that asynchronous function finishes its execution. It is not difficult to see the problem there, in series of asynchronous codes that need to be executed one after the other you end up with various levels of nesting, the so-called callback Hell (did not find a good ref in PT). Promises solve this problem, see how the previous example could be rewritten with the use of them:

algumaFnAsync().then(function() {
    return outraAsyncFn();
}).then(function() {
    return maisUmaAsyncFn();
}).then(function() {
    // código que faz algo após todas  as operações assíncronas terem completado
});

The code is clearer in its intention and the representation is much closer to a synchronous flow where you have "do this, then this, then that". Although they are a non-trivial abstraction Precedents are a good solution to the problem that is the representation of asynchronous flow in code.

7

A promise is a "box" (formally, in Javascript, a object) that encapsulates a value that will be obtained from an asynchronous operation. Generally this value comes from I/O operations, which are usually asynchronous.

The promise, as its name says, "promises" eventually provide a value in a moment unknown in relation to the creation of the promise. How the receipt of this value occurs at a time future in relation to the creation of the promise, it is necessary to use some means to execute some code when the promise has been "finalized" so that the value obtained by the asynchronous operation can be accessed.

The most common way to receive this "promise completion notification" is to use the method then, running a callback through the resolution promise. There is also the method catch, executes a callback through possible rejections of promise.

Thus, promises are a type of structures that allow "encapsulating" asynchronous operations in Javascript. They are much more declarative than callbacks, which was the previous medium used to manage this type of operation in JS. Some say promises are primitive for handling asynchronous operations.

An example:

// Nesse exemplo, a função `readFileString` retorna uma promessa que contém uma string.
// Na notação do TypeScript, o tipo desta promessa seria `Promise<string>`.
// Isto é, quando for resolvida, a promessa fornecerá uma string.
readFileString('./foo.txt')
  .then((fileContents) => {
    // `fileContents` é a string resolvida pela promessa.
    console.log('Conteúdo do arquivo:', fileContents);
  })
  .catch((ioError) => {
    // Eventualmente, a promessa também pode ser rejeitada:
    console.error(ioError);
  });

The promises were natively introduced into Javascript in the Ecmascript 2015 (ES6) edition. There polyfill available, although most of the mainstream browsers support this building natively.


The states of a promise

A promise can be seen as a state machine. In this sense, it is relatively simple, having only three states:

Diagrama de estados de uma promessa

When the promise is created, the state is immediately assumed pending. Eventually, the promise can be "finalized", moving to one of two states:

  • fulfilled, when the promise is settled (most of the time with some value). This usually corresponds to an operation performed without errors.
  • rejected, when the promise is rejected (most of the time with some error). This usually corresponds to an operation that encountered some error.

It is also said that the promise is settled when in the state fulfilled or rejected. From now on, we will treat "finished" promises as promises settled.

A promise can only become settled (resolve or reject) a only time, with a view to fulfilled and reject sane finals. In other words, it is not possible to revert from these states to the pending. The direction of the arrows in the diagram above also highlights the impossibility of retreating pending.


Using promises

There are three main ways to use a promise:

  1. Chaining.
  2. Use the syntax of async and await.
  3. Nesting (not recommended; visually pollutes the code and makes understanding difficult).

Chaining up promises

As we have seen above, any promise has methods such as then or catch, which are used respectively to address the resolution and rejection of the promise.

The interesting thing is, from inside the callback passed to these methods, one can return a value, which shall be used by the (then or catch) to create a new promise, which by it will be returned. This makes possible the chaining of several thens and several catches.

This is due to the relative nature "pure" of the promises, which, although are not totally monadic, allow composition and chaining, factors that comes from functional programming, one of the inspirations for the promise in JS.

Without further ado, an example:

const username = 'lffg';

// `fetch` retorna uma promessa, do tipo `Promise<Response>`:
fetch(`https://api.github.com/users/${username}`)
  .then((response) => {
    // Note que, de dentro deste `then`, estamos retornando o resultado do método
    // `Response.prototype.json`, que converte a `Response` (resultado da promessa
    // anterior) em JSON.
    // O método `json` também retorna uma promessa.
    return response.json();
  })
  // <------------------------------- Note o encadeamento.
  .then((json) => {
    return json.public_repos; // Deste `then`, retornamos número de repositórios do usuário.
  })
  // <------------------------------- Note o encadeamento.
  .then((repoCount) => {
    console.log(`O usuário pesquisado possui ${repoCount} repositórios.`);
  })
  // Por fim, encadeamos um `catch` para tratar eventuais rejeições.
  .catch((error) => {
    console.log('Houve algum erro: ' + error.message);
  });

This chain is at all times possible, given that:

  • If the callback passed to then or catch return a promise, the then (or catch) will make the "planing" (English, flat) of the promise, so as to return a promise of "depth" 1.
  • If the callback passed to then or catch return a promise, the then (or catch) will cause that value to be wrapped in a promise.

More details on Promise.prototype.then and in Promise.prototype.catch.

Using async and await for await promises

The async is an annotation that can be used to mark functions such as asynchronous. Within asynchronous functions, it is possible to use the operator await.

This syntactic construction was introduced in Ecmascript 2017 (ES8). Therefore, in some environments, it may not be possible to use it without a transpilation process.

The async/await has become extremely common since, for many people, it tends to make asynchronous programming using promises of a little easier understanding.

That’s because the async/await give the appearance of synchronism, since, within the asynchronous function, the await blockade the execution until the promise is settled.

  • In case the promise is resolved, the await will unlock the execution and evaluate to the value solved by the promise.
  • In case the promise is rejected, await unblock execution and launch rejection error.

Like the await error thrower in case of rejection, it is possible to use blocks try/catch, which makes the syntax even closer to the idea of synchronization that some programmers have.

Converting the previous example to async/await, we have something like:

const username = 'lffg';

// Note que o `await` só pode ser utilizado DENTRO de funções assíncronas:
(async () => {
  try {
    // O `fetch` retorna uma promessa. Utiliza-se o `await` para aguardar a finalização.
    const response = await fetch(`https://api.github.com/users/${username}`);
    // O método `Response.prototype.json` retorna uma promessa. Portanto, é necessário
    // utilizar o `await`.
    const json = await response.json();

    // Note que como `json` não é uma promessa, não é preciso utilizar o `await`.
    const repoCount = json.public_repos;
    console.log(`O usuário pesquisado possui ${repoCount} repositórios.`);
  } catch (error) {
    console.log('Houve algum erro: ' + error.message);
  }
})();

More details in this other question.


Creating a new promise

A promise is created from the builder Promise, present in the global scope of Javascript. This constructor must be provided with a function called executor of the promise, that will be invoked immediately after the instantiation of the promise.

The executing function takes two functions as parameters, commonly called resolve and reject. When called:

  • The function resolve, when called, resolves the promise (passing it to the state fulfilled) with the value provided.
  • Already reject, when called, rejects the promise (passing it to the state rejected) with the given value. This value is usually an instance of Error.

An example:

// Promessa que será resolvida após 1 segundo de sua criação.
const promiseThatWillFulfill = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Valor de sucesso, vindo de dentro da promessa.');
  }, 1000);
});

// Promessa que será rejeitada após 2 segundos de sua criação.
const promiseThatWillReject = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error('Whoops! Algo deu errado, vindo de dentro da promessa.'));
  }, 2000);
});

//
// Utilização de `promiseThatWillFulfill`:
// =======================================

promiseThatWillFulfill
  // O `then` é um método presente em qualquer promessa:
  .then((resolvedValue) => {
    console.log('Promise `promiseThatWillFulfill` resolvida com: ' + resolvedValue);
  })
  // Como o `then` retorna uma outra promessa, pode-se encadear um `catch`,
  // que também é um método de promessa, para realizar a tratativa da rejeição.
  //
  // Nesse caso, no entanto, o `catch` não será executado, já que sabemos que a
  // promessa `promiseThatWillFulfill` não irá ser rejeitada.
  .catch((error) => {
    console.log('Algo deu errado em `promiseThatWillFulfill`: ' + error.message);
  });

//
// Utilização de `promiseThatWillReject`:
// ======================================

promiseThatWillReject
  // Neste caso, o `then` não será chamado:
  .then((resolvedValue) => {
    console.log('Promise `promiseThatWillReject` resolvida com: ' + resolvedValue);
  })
  // Será rejeitada:
  .catch((error) => {
    console.log('Algo deu errado em `promiseThatWillReject`: ' + error.message);
  });

Most of the time, however, the programmer will not directly use the constructor Promise to create a new promise. This is usually done by library creators, who wish to implement the promise interface for methods, functions etc.

Eventually, however, the use of these constructors can be used to "adapt" old functions or methods that were not done with the promise interface, but rather with callbacks, which was the medium ancient of handling asynchronous Javascript operations (at one time sad in which promises did not yet natively exist). Here’s how to adapt old libraries to the promise interface.

Continuation...

Browser other questions tagged

You are not signed in. Login or sign up in order to post.