Visualizing Promise Arrays
Published: 2022-02-17 23:09:54

```bash
npm install @lrnwebcomponents/promise-progress --save
```
The other day I was working with a student who's making a stepped form for a project we're doing. While explaining her UX pattern, she added in a progress bar with a fun visual; assuming we'd be showing them the progress of the site as it's being built.

"Well, the site is literally just copy and paste on the server via a fetch ...." but as I was thinking about it... we could visualize fetch, especially if we had a lot of them. I set to work combining a few things:
- [`progress` html tag and the `accent-color` attribute](https://twitter.com/claviska/status/1493703271076315141)
- [An article I found on dev.to about displaying progress](https://dev.to/jrdev_/how-to-display-the-progress-of-promises-in-javascript-lh0)
- [My love of web components](https://haxtheweb.org/) and [our portfolio of elements](https://www.npmjs.com/search?q=%40lrnwebcomponents)

Our team loves [Lit]([https://lit.dev/) so we built on top of that a few different implementations. One that's just Lit, one that uses our color library, and one that uses our [autoloader registry](https://dev.to/btopro/uwc-part-4-dynamic-hydration-5hco) to simplify references.

## Example 1 - basic lit
This is a very simple example that will load 7 `Promise` responses from an `Array` of `function`s

{% embed https://codepen.io/btopro/pen/ZEavVbN %}

## Example 2 - playing with color variables and css
This shows some more CSS work that's inline with what the final thing will be when we use it in our project.

{% embed https://codepen.io/btopro/pen/eYeyPWW %}

## How it works
- [Full source](https://github.com/elmsln/lrnwebcomponents/blob/master/elements/promise-progress/lib/promise-progress-lite.js)
- [npm: promise-progress](https://www.npmjs.com/package/@lrnwebcomponents/promise-progress)
- [upkg](https://unpkg.com/@lrnwebcomponents/promise-progress?module)
```js
<progress
  part="progress"
  max="${this.max}"
  value="${this.value}"
></progress>
}
```
Our render method is excessively simple. All it does is take the max possible and the value. It's a sweet, simple vanilla HTML tag. All the magic is in The Platform (tm) itself.

To obtain and update value and max, we set a list. The `list` must be an `Array` of `function`s that return a `Promise`. The awesome thing about this is that dynamic `import()` and `fetch()` return `Promise` already! This means that with very little effect and wiring, we have a pathway to display how quickly links are being requested, json data accessed, or dynamic imports are loaded!

In our code pens, we pass in data that looks like this:
```js
[
    () => import("https://unpkg.com/@lrnwebcomponents/meme-maker?module"),
    () => fetch("https://images-api.nasa.gov/search?q=apollo%2011&description=moon%20landing&media_type=image"),
    () => fetch("https://google.com/"),
    () => fetch("https://github.com/"),
]
```

This shorthand is an array of functions with each function running an `import()` or `fetch()`. Another more verbose way could be to write:
```js
[
  function() { return import("https://unpkg.com/@lrnwebcomponents/meme-maker?module");},
```
This methodology of an array of functions that return a Promise means that we can wire this tag up to any long running task we need to and we'll be updated via `Event` as to when the series of tasks has completed. It's a great way of visualizing a batch of tasks!

After we have our `.list` Array populated in the element, it's time to actually process the list. Running `.process()` when we're ready does all the heavy lifting of Promise resolution by looping through a `.map` and running each function in a row.
```js
async process() {
    const list = this.list;
    if (this.canLoad) {
      var count = 0;
      const promises = await list.map(async (item) => {
        return await item()
          .then((res) => {
            count = count + 1;
            this.value = Math.round((count / this.list.length) * 100);
            this.loadingBar.textContent = `Loading ${this.value} of ${this.max}`;
            resolve(res);
          })
          .catch((err) => {
            // an error occured
            reject(err);
          });
      });
      await Promise.allSettled(promises).then(() => {
        this.loadingBar.textContent = `Loading Finished`;
        this.value = this.max;
        setTimeout(() => {
          this.dispatchEvent(
            new CustomEvent("promise-progress-finished", {
              detail: {
                value: true,
              },
            })
          );
        }, 100);
      });
    }
```
The `Promise.allSettled` allows us to hit 100% each time even if certain parts of the payload failed to deliver / resolve. I went that route because I don't feel it's the responsibility of this to actually handle the error but just to communicate the attempt to complete.

Want to learn more about topics like this in webcomponents? Curious what the HAX team is up to? Join, Learn and Grow together with us May 9/10th in State College for [HAXcamp](https://hax.camp/)!