---
description: >-
  https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/
---

# Sequentially Resolve Promises Works, Giải quyết tuần tự các lời hứa hoạt động (ok)

<figure><img src="https://2494213435-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LYjAG13rAPdbArSfRyB%2Fuploads%2F5xhl7Sdbumm8yc3zh7Ci%2Fimage.png?alt=media&#x26;token=b2e99f48-aa49-4f9c-8f64-7f85b96750bd" alt=""><figcaption></figcaption></figure>

D:\Tiah\working\MARUNO-21\test.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.7/dayjs.min.js"></script>
</head>
<body>
  <script>
  function methodThatReturnsAPromise(nextID) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log(`Resolve! ${dayjs().format('hh:mm:ss')}`);
        resolve();
      }, 1000);
    });
  }
  [1, 2, 3].reduce((accumulatorPromise, nextID) => {
    console.log(`Loop! ${dayjs().format('hh:mm:ss')}`);
    return accumulatorPromise.then(() => {
      return methodThatReturnsAPromise(nextID);
    });
  }, Promise.resolve());
  </script>
</body>
</html>
```

[`Hoặc`](https://gist.github.com/anvk/5602ec398e4fdc521e2bf9940fd90f84)

```
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.7/dayjs.min.js"></script>
</head>
<body>
  <script>
  function asyncFunc(e) {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(e), e * 1000);
    });
  }
  const arr = [1, 2, 3];
  let final = [];
  function workMyCollection(arr) {
    return arr.reduce((promise, item) => {
      return promise
        .then((result) => {
          console.log(`item ${item}`);
          return asyncFunc(item).then(result => final.push(result));
        })
        .catch(console.error);
    }, Promise.resolve());
  }
  workMyCollection(arr)
    .then(() => console.log(`FINAL RESULT is ${final}`));
  </script>
</body>
</html>
```

## Why Using reduce() to Sequentially Resolve Promises Works

![Avatar of Alex MacArthur](https://secure.gravatar.com/avatar/e39d6cb16789125a8ef3113bdeaabf49?s=80\&d=retro\&r=pg)[Alex MacArthur ](https://css-tricks.com/author/alexmacarthur/)on Oct 17, 2018

DigitalOcean provides cloud products for every stage of your journey. Get started with [$200 in free credit!](https://try.digitalocean.com/css-tricks/?utm_medium=content_acq\&utm_source=css-tricks\&utm_campaign=global_brand_ad_en\&utm_content=conversion_prearticle_everystage)

Writing asynchronous JavaScript without using the `Promise` object is a lot like baking a cake with your eyes closed. It can be done, but it’s gonna be messy and you’ll probably end up burning yourself.

I won’t say it’s _necessary_, but you get the idea. It’s real nice. Sometimes, though, it needs a little help to solve some unique challenges, **like when you’re trying to sequentially resolve a bunch of promises in order, one after the other.** A trick like this is handy, for example, when you’re doing some sort of batch processing via AJAX. You want the server to process a bunch of things, but not all at once, so you space the processing out over time.

Ruling out packages that help make this task easier (like Caolan McMahon’s [async](https://github.com/caolan/async) library), the most commonly suggested solution for sequentially resolving promises is to use `Array.prototype.reduce()`. You might’ve heard of [this one](https://css-tricks.com/understanding-the-almighty-reducer/). Take a collection of things, and reduce them to a single value, like this:

```javascript
let result = [1,2,5].reduce((accumulator, item) => {
  return accumulator + item;
}, 0); // <-- Our initial value.

console.log(result); // 8
```

But, when using `reduce()` for our purposes, the setup looks more like this:

```javascript
let userIDs = [1,2,3];

userIDs.reduce( (previousPromise, nextID) => {
  return previousPromise.then(() => {
    return methodThatReturnsAPromise(nextID);
  });
}, Promise.resolve());
```

Or, in a more modern format:

```javascript
let userIDs = [1,2,3];

userIDs.reduce( async (previousPromise, nextID) => {
  await previousPromise;
  return methodThatReturnsAPromise(nextID);
}, Promise.resolve());
```

This is neat! But for the longest time, I just swallowed this solution and copied that chunk of code into my application because it “worked.” This post is me taking a stab at understanding two things:

1. Why does this approach even work?
2. Why can’t we use other `Array` methods to do the same thing?

#### Why does this even work?

Remember, the main purpose of `reduce()` is to “reduce” a bunch of things into one thing, and it does that by storing up the result in the `accumulator` as the loop runs. But that `accumulator` doesn’t have to be numeric. The loop can return whatever it wants (like a promise), and recycle that value through the callback every iteration. Notably, no matter what the `accumulator` value is, the loop itself never changes its behavior — including its pace of execution. It just keeps rolling through the collection as fast as the thread allows.

This is huge to understand because it probably goes against what you think is happening during this loop (at least, it did for me). **When we use it to sequentially resolve promises, the `reduce()` loop isn’t actually slowing down at all.** It’s completely synchronous, doing its normal thing as fast as it can, just like always.

Look at the following snippet and notice how the progress of the loop isn’t hindered at all by the promises returned in the callback.

```javascript
function methodThatReturnsAPromise(nextID) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {

      console.log(`Resolve! ${dayjs().format('hh:mm:ss')}`);

      resolve();
    }, 1000);
  });
}

[1,2,3].reduce( (accumulatorPromise, nextID) => {

  console.log(`Loop! ${dayjs().format('hh:mm:ss')}`);

  return accumulatorPromise.then(() => {
    return methodThatReturnsAPromise(nextID);
  });
}, Promise.resolve());
```

In our console:

```markdown
"Loop! 11:28:06"
"Loop! 11:28:06"
"Loop! 11:28:06"
"Resolve! 11:28:07"
"Resolve! 11:28:08"
"Resolve! 11:28:09"
```

The promises resolve in order as we expect, but the loop itself is quick, steady, and synchronous. After looking [at the MDN polyfill](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Polyfill) for `reduce()`, this makes sense. There’s nothing asynchronous about a `while()` loop triggering the `callback()` over and over again, which is what’s happening under the hood:

```javascript
while (k < len) {
  if (k in o) {
    value = callback(value, o[k], k, o);
  }
  k++;
}
```

With all that in mind, the real magic occurs in this piece right here:

```javascript
return previousPromise.then(() => {
  return methodThatReturnsAPromise(nextID)
});
```

Each time our callback fires, we return a promise that resolves to _another_ promise. And while `reduce()` doesn’t wait for any resolution to take place, **the advantage it does provide is the ability to pass something back into the same callback after each run**, a feature unique to `reduce()`. As a result, we’re able build a chain of promises that resolve into more promises, making everything nice and sequential:

```javascript
new Promise( (resolve, reject) => {
  // Promise #1
  
  resolve();
}).then( (result) => { 
  // Promise #2
  
  return result;
}).then( (result) => { 
  // Promise #3
  
  return result;
}); // ... and so on!
```

All of this should also reveal why we can’t just return a _single, new_ promise each iteration. Because the loop runs synchronously, each promise will be fired immediately, instead of waiting for those created before it.

```javascript
[1,2,3].reduce( (previousPromise, nextID) => {

  console.log(`Loop! ${dayjs().format('hh:mm:ss')}`);
  
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`Resolve! ${dayjs().format('hh:mm:ss')}`);
      resolve(nextID);
    }, 1000);
  });
}, Promise.resolve());
```

In our console:

```markdown
"Loop! 11:31:20"
"Loop! 11:31:20"
"Loop! 11:31:20"
"Resolve! 11:31:21"
"Resolve! 11:31:21"
"Resolve! 11:31:21"
```

Is it possible to wait until _all_ processing is finished before doing something else? Yes. The synchronous nature of `reduce()` doesn’t mean you can’t throw a party after every item has been completely processed. Look:

```javascript
function methodThatReturnsAPromise(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`Processing ${id}`);
      resolve(id);
    }, 1000);
  });
}

let result = [1,2,3].reduce( (accumulatorPromise, nextID) => {
  return accumulatorPromise.then(() => {
    return methodThatReturnsAPromise(nextID);
  });
}, Promise.resolve());

result.then(e => {
  console.log("Resolution is complete! Let's party.")
});
```

Since all we’re returning in our callback is a chained promise, that’s all we get when the loop is finished: a promise. After that, we can handle it however we want, even long after `reduce()` has run its course.

#### Why won’t any other Array methods work?

Remember, under the hood of `reduce()`, we’re not waiting for our callback to complete before moving onto the next item. It’s completely synchronous. The same goes for all of these other methods:

* [`Array.prototype.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Polyfill)
* [`Array.prototype.forEach()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Polyfill)
* [`Array.prototype.filter()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Polyfill)
* [`Array.prototype.some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some#Polyfill)
* [`Array.prototype.every()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every#Polyfill)

**But `reduce()` is special.**

We found that the reason `reduce()` works for us is because we’re able to return something right back to our same callback (namely, a promise), which we can then build upon by having it resolve into another promise. With all of these other methods, however, we just can’t pass an argument to our callback that was returned _from_ our callback. Instead, each of those callback arguments are predetermined, making it impossible for us to leverage them for something like sequential promise resolution.

```javascript
[1,2,3].map((item, [index, array]) => [value]);
[1,2,3].filter((item, [index, array]) => [boolean]);
[1,2,3].some((item, [index, array]) => [boolean]);
[1,2,3].every((item, [index, array]) => [boolean]);
```

#### I hope this helps!

At the very least, I hope this helps shed some light on why `reduce()` is uniquely qualified to handle promises in this way, and maybe give you a better understanding of how common `Array` methods operate under the hood. Did I miss something? Get something wrong? Let me know!
