Forever Functional: Waiting for some promises?
Tags: webdev, javascript, typescript
Published: 2023-01-29 17:49:34


by [Federico Kereki](https://blog.openreplay.com/forever-functional-waiting-for-some-promises/)


Let's start this article with a programming challenge I sometimes pose when interviewing potential developers. You're asked to write a service that will accept a city name and return the current temperature there. To do this, you may call three third-party weather APIs, and as soon as you get one answer, reply with it. How do you do this? 

If you are aware of JavaScript's [promise concurrency methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#promise_concurrency), the answer is easy: generate promises that call each weather API, and use [`Promise.any()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) to get the needed value from the first promise that is fulfilled. 

A variation: what if I had asked instead to wait until *all* weather APIs had answered and then reply with the average of the three temperatures? Easy, too: you would have used [`Promise.all()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) instead.

Now, here comes the question: what if I had asked you to wait until *two* weather APIs had answered and then return the average of both temperatures? There's no method for this; we need a `Promise.some()` that will allow you to wait for some --not one, not all-- of a set of promises, and this is what we'll do in this article.

## Planning our new method

Before we start coding our `Promise.some()` method, we have to answer two questions: 

* what should the parameters for this method be?
* what should the method return?

Both `Promise.any()` and `Promise.all()` take an iterable of promises as their (only) parameter, so that should be our first parameter too. We'll need a second one to specify how many promises should be fulfilled, so we're looking at `Promise.some(promisesArray, fulfilledCount)`; easy!

For the second question, let's do some analysis:

* `Promise.any()` is fulfilled when one of the array's promises is fulfilled and is rejected when all those promises are rejected. When fulfilled, the returned value is whatever the fulfilled promised returned, and when rejected, it returns an `AggregateError` with all the rejection reasons.

* `Promise.all()` is fulfilled when all the array's promises are fulfilled and is rejected if just one of those promises is rejected. When fulfilled, the returned value is an array with all the fulfilled values, and when rejected, it returns an error with the first rejection reason.

* Both methods apply short-circuit evaluation: `Promise.any()` is fulfilled as soon as any promise is fulfilled, and `Promise.all()` is rejected as soon as any promise is rejected.

So, now we can define `Promise.some(promises, count)` will be fulfilled if `count` promises are fulfilled and rejected otherwise. When fulfilled, the returned value is an array with the values returned by the fulfilled promises, and when rejected, it returns an `AggregateError` with the reasons for the rejected promises.

We should also do short-circuit evaluation: `Promise.some()` should be fulfilled the moment enough promises are fulfilled, and it should be rejected if too many promises are rejected, making it impossible to get enough fulfilled promises.

With this definition, `Promise.some(promises, promises.length)` would be exactly equivalent to `Promise.all(promises)`. On the other hand, `Promise.some(promises, 1)` would not be equal to `Promise.any(promises)` -- the difference being that a fulfilled `.any()` returns a single, but `.some()` returns an array of values; in this case, a single one.

## Coding our new method

Now we know what we want, let's see how to implement one. We can already start by modifying the `Promise.prototype` object:

```javascript
Promise.some = function (promises, count) {
  return promiseSome(promises, count);
};
```

(What if you don't want to modify a global object, which is usually something you shouldn't do? In that case, directly use the `promiseSome()` function, and everything will work.)

Obviously, there's a small detail -- what is the `promiseSome()` function? We will have to build a new promise out of all the promises in the array. We'll also have to keep track of the individual promises as they resolve or reject. The new promise will resolve or reject only when enough of the individual promises have been resolved or rejected. This code will do:

```javascript
function promiseSome(promises, count) {                        // (1)
  return new Promise((resolve, reject) => {                    // (2)
    const resolved = [];                                       // (3)
    const rejected = [];                                       // (4)

    promises.forEach((promise, index) => {                     // (5)
      promise                                    
        .then((val) => {                                       
          resolved.push(val);                                  // (6)
          if (resolved.length === count) {                     
            resolve(resolved);                                 // (7) 
          }
        })
        .catch((err) => {
          rejected.push(err);                                  // (8)
          if (rejected.length > promises.length - count) {     
            reject(new AggregateError(rejected));              // (9)
          }
        });
    });
  });
```

Let's analyze how this works. The `promises` array has all the individual input promises of which we want `count` to resolve (1). We create a new promise (2) that will resolve or reject depending on what the input promises do. We will use two arrays, `resolved` (3) and `rejected` (4), for the results of promises as they either resolve or reject. For each of those promises (5), if it resolves, we'll store the result in the `resolved` array (6), and if enough promises got resolved, we'll resolve our new promise with the `resolved` array (7).

If an individual promise rejects, we'll do a similar job, but we'll store the reason for rejection in the `rejected` array (8). If too many promises have rejected (making it impossible to achieve `count` resolved ones), we'll reject our new promise with an `AggregateError` with the array of reasons (9). We are done!


<h2>Session Replay for Developers</h2> 
<p><em>
Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data
</em></p>
<img alt="OpenReplay" width="768" height="400" src="https://blog.openreplay.com/assets/overview_Z17Qdyg.png" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em>







## Testing our new method

To test this, we'll require some fake promises.

```javascript
const success = (time, value) =>
  new Promise((resolve) => setTimeout(resolve, time, value));

const failure = (time, reason) =>
  new Promise((_, reject) => setTimeout(reject, time, reason));
```

Our `success()` function returns a promise that, after some time, will resolve to a given `value`. Similarly, the `failure()` function returns a promise that, after time, will reject with a `reason`. With these functions we can run some checks, which would be directly turned into Jest if we wanted to.

```javascript
Promise.some([success(2000, 1), success(1500, 2), success(500, 3)], 2).then(
  (r) => console.log("Test #1: Should succeed with 2 and 3", r)
);

Promise.some([success(2100, 4), failure(400, 5), success(600, 6)], 1).then(
  (r) => console.log("Test #2: Should succeed with 6", r)
);

Promise.some([success(2200, 7), failure(300, 8), success(700, 9)], 3).catch(
  (r) => console.log("Test #3: Should reject with 8", r)
);

Promise.some([success(900, 10), failure(1800, 11), failure(800, 12)], 2).catch(
  (r) => console.log("Test #4: Should reject with 11 and 12", r)
);
```

Running this produces the following:

```
Test #3: Should reject with 8 AggregateError {
  [errors]: [ 8 ]
}
Test #2: Should succeed with 6 [ 6 ]
Test #1: Should succeed with 2 and 3 [ 3, 2 ]
Test #4: Should reject with 11 and 12 AggregateError  {
  [errors]: [ 12, 11 ]
}

```

Let's check. 
* Test #3 fails at time 300ms, when it's clear that we won't be able to resolve enough promises.
* Test #2 succeeds at time 600ms, when a single promise is resolved, despite already having had a failure at time 400ms.
* Test #1 is next; at time 1500ms two promises are resolved. 
* Test #4 is last, at time 1800ms, when we get a second rejection.

We could do more tests with more functions, but it seems we're on the right way, and our `Promise.some()` method is working the way we want.

## Typing our method

If you're using TypeScript, you'll need to define some typing, which has some interesting details. First, we'll have to `npm install aggregate-error` so TypeScript will accept using `AggregateError()`, and we'll be able to add typing to `promiseSome()`.

```typescript
function promiseSome(promises: Promise<any>[], count: number): Promise<any[]> {
  return new Promise((resolve, reject) => {
    const resolved: any[] = [];
    const rejected: any[] = [];

    promises.forEach((promise, index) => {
        .
        .
        .
    });
  });
}
```

Types are straightforward: we get an array of promises of any type and a number, and we produce a promise that will resolve to an array of promises. The `resolved` and `rejected` arrays may have any types, so that's not complex either. There is a problem, though, if you want to modify the `Promise` global object. The solution requires adding a definition to the [`PromiseConstructor` interface](https://github.com/microsoft/TypeScript/blob/main/lib/lib.es2015.promise.d.ts) as follows:

```typescript
declare global {
  interface PromiseConstructor {
    some(promises: Promise<any>[], count: number): Promise<any[]>;
  }
}
```

We are saying now that any promise will also have an additional `some()` method, and we can do the following.

```typescript
Promise.some = function (promises: Promise<any>[], count: number) {
  return promiseSome(promises, count);
};
```

If you want to run the tests again, you'll need typing for our fake promises, but that's not hard. We'll have two generic functions, depending on a type parameter `T`, as follows.

```javascript
const success = <T>(time: number, value: T): Promise<T> =>
  new Promise((resolve) => setTimeout(resolve, time, value));

const failure = <T>(time: number, reason: T): Promise<T> =>
  new Promise((_, reject) => setTimeout(reject, time, reason));
```

We're done!

## Conclusion

In this article, we've seen a problem related to handling several promises, and we added a new method to promise objects to solve it, which we tested with a number of cases. For completeness, we also saw how to convert our code to TypeScript, so we can use it anywhere. A win all around!

> A TIP FROM THE EDITOR: This series of articles has more on promises; check out [Waiting With Promises
](https://blog.openreplay.com/forever-functional-waiting-with-promises/) and [Memoizing Promises](https://blog.openreplay.com/forever-functional-memoizing-promises/).



[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)