Are you sure you know Promises?
Tags: javascript, beginners
Published: 2022-06-29 22:09:00

I am reviewing other developers' code for a long time by now and saw lots of little mistakes and misunderstandings about how `async` code works in the language.

I think most people learning `javascript` _eventually_ gets `Promises`, but there are lots of tiny edge-cases and nitty-gritty details. There is the _high level understanding_ of the syntax and semantics of it, but since it's no trivial matter, it requires the _"gut instinct"_ as well to be developed. You need to train your _natural neural network_ with a long list of examples to develop this ability.


So here it is! An exhaustive list of many different cases (trivial and not so trivial) for people who learnt or are already familiar with `Promises` and are looking for nailing them.

## How you should use the exercises
First, I will define a very simple function, that creates a new _"simulated process"_, that outputs its ID and its start and finish. Afterwards it returns its `id`, that can be used for later processing.

**Copy** that into a `browser console` and **execute** it.

👀 Then **look** at the example.

🤔 Try to **guess** in what order will be the logs arriving to the output. 

✅ When you made your guess copy-paste the example to the console and **run** it.

> Found something that confused you? You have found a blind-spot! If you cannot work out what happened, you can ask me in the comments and I will explain! 😊

### Note on guessing first
It is important to not just _copy-paste_ these examples but to guess the results beforehand! The research is quite steady on [Test-enhanced learning](https://cft.vanderbilt.edu/guides-sub-pages/test-enhanced-learning-using-retrieval-practice-to-help-students-learn/); so first you work your brain and then see if you were right.

```javascript
function createProcess(id, timeout) {
  console.log(`🏃‍️ #${id}: Process started!`);

  return new Promise(resolve => {
    function runTinyFakeProcess() {
      resolve(id);
      console.log(`✅ #${id}: Process finished!`);
    }

    setTimeout(runTinyFakeProcess, timeout);
  });
}
```

## Examples
### Example 1
Which process will finish first? Which will be the last?
Look for the ✅!

```javascript
createProcess(1, 3000);
createProcess(2, 2000);
createProcess(3, 1000);
```

### Example 2
```javascript
createProcess(1, 3000);
await createProcess(2, 2000);
createProcess(3, 1000);
```

### Example 3
```javascript
await createProcess(1, 3000);
await createProcess(2, 2000);
await createProcess(3, 1000);
```

### Example 4
```javascript
await createProcess(1, 3000).then(() => {
  createProcess(2, 2000);
  createProcess(3, 1000);
});
```

### Example 5
```javascript
await createProcess(1, 3000)
  .then(() => createProcess(2, 2000))
  .then(() => createProcess(3, 1000));
});
```

### Example 6
```javascript
await createProcess(1, 3000)
  .then(() => {
    createProcess(2, 2000);
  })
  .then(() => createProcess(3, 1000));
```

_Confused about the result and can't see the difference? Check for the `{}` around the second call!_

### Example 7
```javascript
await Promise.all([
  createProcess(1, 3000), 
  createProcess(2, 2000), 
  createProcess(3, 1000)
]);
```

### Example 8
```javascript
await Promise.all([
  createProcess(1, 3000)
    .then(() => createProcess(2, 2000)),
  createProcess(3, 1000)
]);
```

### Example 9
```javascript
await Promise.all([
  await createProcess(1, 3000), 
  await createProcess(2, 2000), 
  await createProcess(3, 1000)
]);
```

### Example 9b
Got confused about example #9? Let's break the previous one down:

```javascript
// But how??? Think about initialization!
const processes1 = [
  await createProcess(1, 3000), 
  await createProcess(2, 2000), 
  await createProcess(3, 1000)
];

console.log('Are the processes already done?');

// At this point all 3 processes will already be awaited
// and this call will immediately return the already 
// processed results.
await Promise.all(processes1);
```

### Example 10
Let's do something with the returned values!

```javascript
const processes2 = [
  createProcess(1, 3000), 
  createProcess(2, 2000), 
  createProcess(3, 1000)
];

await Promise.all(processes2.map((item) => item * 2));
```

### Example 11
How did we get `NaN`s? 🤔 And how do get the proper answer?

Look!

```javascript
const processes3 = [
  createProcess(1, 3000), 
  createProcess(2, 2000), 
  createProcess(3, 1000)
];

// first we wait for the processes to finish
(await Promise.all(processes3))
// then we just toy with the results
  .map(result => result * 2));
```

### Example 11b
Could we have done just this?
```javascript
const processes4 = [
  createProcess(1, 3000),
  createProcess(2, 2000),
  createProcess(3, 1000)
];

await Promise.all(processes4.map(async (promise) => {
  const result = await promise;
  return result * 2;
}));
```

Actually, yes, we could! But check for the sublety!
In `#11`, we waited for all promises to first run to completion and then we processed the result. 

In `#11b` we are immediately acting upon the promises: we await them, process their result. Then `Promise.all` will wait for the new promises defined by the `async` `.map` function which in turn would yield the processed results.

### Example 11c
Does creating processes and awaiting them in a `.map()` would make them run in series instead of in parallel? 🤔 

```javascript
const values = [
  { id: 1, time: 3000 }, 
  { id: 2, time: 2000 }, 
  { id: 3, time: 1000 }
];

// try it!
await Promise.all(
  values.map(async ([id, time]) => 
    await createProcess(id, time)
  )
);
```

No, don't worry! During runtime when `await` is encountered, the "processing" of the code block will be suspended and another block can be processed. In other words, after `createProcess` started processing at the moment of `await`, we switch to another iteration of `.map`, which will in turn start another process.

### Example 12
Let's check on [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race)!

```javascript
await Promise.race([
  createProcess(1, 2000),
  createProcess(2, 1000)
]);
```

Who would "win"?
#### Note
Pay attention to the logs as well! 
See anything strange? 

Even though `#2` was the first and the result of `#1` is ignored, `#1` also ran to completion! In case process `#1` must stop when process `#2` "won" the race, then you need
something like a [cancellable promise](https://github.com/srmagura/real-cancellable-promise)! (There are other ways... You can do your own research by searching for [_"Why can't you cancel promises?"_](https://stackoverflow.com/a/30235261)).


### Example 13
```javascript
await Promise.race([
  createProcess(1, 2000)
    .then(() => createProcess(3, 3000)), 
  createProcess(2, 1000)
]);
```

See my previous point? The entire chain ran (defined in the first item in the array), even though process `#2` already "won" the race.


### Example 14
What will be the last message? For this exercise I am going to pass in some arrays for IDs (I know it's not clean code), because `createProcess` merely returns the first parameters.

Therefore this time we can use the awaited return values to start and wait for even more processes.

So what this really should do is waiting for some array data and then run processes for each item in each arrays.

And after everything ran we would like to have have a success message. But did we succeed? 🧐

```javascript
const processes5 = [
  createProcess([1, 2, 3], 1000),
  createProcess([4, 5, 6], 2000),
  createProcess([7, 8, 9], 3000)
];

function runPromiseAll1() {
  return Promise.all(
    processes5.map(async process => {
      const ids = await process;

      await Promise.all(
        ids.map((id, index) => {
          createProcess(`${id}->${index}`, Math.random() * 5000)
        })
      );
    })
  );
}

await runPromiseAll1();
console.log('👆 Must be the last message! We waited for everything!');

```

Is it though?

#### 14b - making order

Let's see if we can make that last `console.log` fired at the right time:
```javascript
function runPromiseAll2() {
  return Promise.all(
    processes5.map(async process => {
      const ids = await process;

      return await Promise.all(
        ids.map(async (id, index) => {
          return await createProcess(`${id}->${index}`, Math.random() * 5000);
        })
      );
    })
  );
}

await runPromiseAll2();
console.log('👆 This _really_ should be the last message after we waited for everything!');
```

#### 14c: the `flatMap` version
And now let's see a _"smart"_ version:

```javascript
function runPromiseAll3() {
  return Promise.all(
    processes5
      .flatMap(async process => await process)
      .map((id, index) => createProcess(`${id}->${index}`, Math.random() * 5000))
  );
}

await runPromiseAll3();
console.log('👆 Finished!');
```

Wait... What? The problem is that `async` code always wraps the result in a `Promise`, so even you return an array it will be _always_ a `Promise`.

#### 14d: the _"Let's unpack the array first"_ version
Let me confess that I needed to try out a couple of things before I could fix this. Async programming combined with arrays in JS is not an easy task. 

```javascript
async function runPromiseAll4() {
  // we unpack the array from the Promise
  const ids = await Promise.all(processes5);

  // and now we can use whatever array 
  const newPromises = ids
      .flatMap(id => id)
      .map(item => { console.log(item); return item; })
      .map((id, index) => createProcess(`${id}->${index}`, Math.random() * 5000));

  return Promise.all(newPromises);
}

await runPromiseAll4();
console.log('😅 Finished!');
```

### Summary
If you find async programming hard, you are right! I think most of us gets the simple notion of some code blocks are running in parallel, however using a simple document to express it is a harder task. There are no visual aids on which blocks would run to completion, which can run in parallel. You have to really-really pay attention to the fine details of the code.

And I think it is a language design problem as well. Until we find a better to express it, we should hone the sharpness of our wits and eyes!