Promises aren't just a way to deal with async operations...
Tags: javascript, typescript
Published: 2021-05-11 13:57:39

Oh, hey there!

So you think you understand Promises, huh?

In JavaScript, Promises are both a way to handle asynchronous operations and a data structure.

This article is a transcript of a [Youtube video I made](https://youtu.be/InU9uv79p48).

Whether you are making a HTTP request, querying a database or writing to the console, I/O operations can
be very slow. Because JavaScript is single-threaded by design -- can only do one thing at a time -- asynchronous
or async operations are very common. 
Let me give you an example, say that when a user of a web app clicks on a button that trigger a HTTP request to 
an API -- the JavaScript runtime had to wait for the request to resolve before handling any other operation,
it would make for a pretty sluggish experience. 
Instead the engine makes the request, put it aside and gets ready to handle any other operations. Every now and then, 
the process will look at the request -- and be like "are you done yet?". When the request finally resolves, the engine 
will execute a function defined by the developer to handle the response. 
You might know those as "callback functions".

A good example of this is `setTimeout`. It's a function that takes another function as argument which will be
executed asynchronously later.

```js
console.log("Before...");

setTimeout(() => console.log("...One second later"), 1000);

console.log("...After");
```

Callbacks works just fine in many cases but starts becoming especially difficult to deal with when multiple 
inter-dependant async operations are needed.

```js
retrieveCurrentUser((error, user) => {
  if (error) return handleError(error);
  
  setCurrentUserStatus(user.ID, "active", (error) => {
    if (error) return handleError(error);
    
    retriveActiveThreadsForUser(user.ID, 10, (error, threads) => {
      if (error) return handleError(error);
      
      threads.forEach(thread => subscribeToThread(thread.ID, user.ID, error => handleError(error)));
    });
  });
});
```

So let's talk about Promises. I mentioned earlier that a Promise is both a way to deal with async operations and a data 
structure. Here's what I meant by that.

Imagine you have the number `42` that you assign to `x`. From this point on, `x` refers to the number `42` and can be 
used as such. Imagine a function called `f` that simply multiplies by 2 any number. Now, if we were to pass `x` to the 
function `f`, it would produce a new number, `84`, that we can assign to the variable `y`. From then on, `y` is `84`.

```js
const f = x => x * 2;
const x = 42;
const y = f(x);
```

A Promise represents a value that may or may not exists yet. If we assign `p` as the Promise of `42`, you can also, say 
that `p` refers to the number `42` and be used as such. The difference is that because `p` may or may not be `42` just 
yet -- remember async operations -- so; the value, `42`, can't be accessed directly. 
We use the `.then` method to access and transform the value. 
Similar to our previous example, if we have a function `f` that multiplies any number by 2, and we apply it to our 
Promise `p`, it would produce a new Promise of the value `84` which we can assign to the variable `q`. From then on, `q` 
is a Promise of the number `84`. It is important to note that `p` is still a Promise of `42`. 

```js
const f = x => x * 2;
const p = Promise.resolve(42);
const q = p.then(f);
```

So now, what if we have a function called `g` that takes any number, multiplies it by 2, but returns a Promise of the 
result? After we apply the function `g` to our Promise `p` -- which is still `42`, we still end up with a Promise of 
`84`. 

```js
const g = x => Promise.resolve(x * 2);
const r = p.then(g);
```

The rule is that if a function returns a value that is not a Promise, the value will be wrapped in a new Promise. But 
if the value is already a Promise, we don't need to wrap it again!

A Promise represents a value that may or may not exists yet. But it also represents the status of the async operation. 
A Promise can either be resolved or rejected. The `.then` method actually accepts two functions as argument. The first 
one, for the happy-path, if the operation has resolved. The second one to handle any error that may have occured.

```js
mysteriousAsyncOperation()
  .then(
    handleSuccess,
    handleError
  );
```

Because Promises are often chained together, there is also a `.catch` method that accepts a function to handle the first
error that occurs, breaking the chain.

```js
mysteriousAsyncOperation()
  .then(secondMysteriousAsyncOperation)
  .catch(handleError);
```

A rejected Promise that has been "caught" always returns a resolved Promise.

```js
mysteriousAsyncOperation()
  .then(secondMysteriousAsyncOperation)
  .catch(error => alert("¯\_(ツ)_/¯‍"))
  .then(() => alert("Everything is fine actually."));
```

Now, let's go back to our previous example with multiple inter-dependant async operations...

```js
const $user = retrieveCurrentUser();
const $threads = userPromise.then(
  user => setCurrentUserStatus(user.ID, "active")
    .then(() => retriveActiveThreadsForUser(user.ID, 10))
);

Promise.all([ $user, $threads ])
  .then(([ user, threads ]) => Promise.all(threads.map(thread => subscribeToThread(thread.ID, user.ID))))
  .catch(error => alert("Something went wrong."));
```

And from then on, `$user` and `$threads` still represent the initial values and can be used again and again without any 
unnecessary nesting.

```js
$threads.then(threads => threads.forEach(thread => {
  const e = document.createElement("iy-thread");
  e.value = thread;
  document.body.appendChild(e);
}));
```

Through my examples, you might have noticed that you can factorize a resolved Promise with the `Promise.resolve` 
function. You can deduct that there's also a `Promise.reject` function. These functions are useful when you need a quick
way to get a Promise. 
 But, if you want to create a Promise from an async operation you will need the Promise constructor.

```js
function wait (d) {
  
  return new Promise(resolve => setTimeout(resolve), d);
}

wait(1000)
  .then(() => alert("Waited one second..."));
```

The Promise constructor handler function also passes a `reject` function as the second argument.

```js
function waitOrThrow (d) {
  
  return new Promise((resolve, reject) => {
    if (Math.random() > 0.5) reject(new Error("Better change next time."));
    else setTimeout(resolve, d);
  });
}

waitOrThrow(1000)
  .then(
    handleSuccess,
    handleError
  );
```

A Promise is a data structure that represents any type of value which may or may not exists yet. 
The Promise protects the value to be accessed directly. 
A handler function can be defined to access and transform the value.
When the handler function returns a value, it creates a new Promise for this value.
In modern JavaScript, understanding and mastering Promises is a very important skill! 
They look at lot more scary than they are. But I swear, Promises are your friend.