Skip to content
This repository has been archived by the owner on Mar 9, 2022. It is now read-only.

Balance readability and performance for async operations

License

Notifications You must be signed in to change notification settings

LiveRamp/higher-order-promise

Repository files navigation

Higher Order Promises

Coverage

This package allows you to fluently parallelize asynchronous calls, while retaining type information, and deferring any actual awaiting until your choosing via calling yield().

In english, this means you will spend less time wrangling Promises, and your code will perform better than if you were to naively use awaits.

For examples of usage, check out its tests.

Installation

If you'd like to fiddle around with it, try it out on RunKit!

This package is distributed using NPM. To use in your project:

npm install @liveramp/higher-order-promise

In-depth justification

The usual way of handling asynchronous computation in Typescript is leveraging async/await. This is reasonably intuitive while keeping you out of callback hell.

Consider the following snippet:

async function awaitTestSequence() {
  let result1 = await slowPromise("1", 1000, 10);
  let result2 = await slowPromise("2", 1000, 20);

  return result1 + result2
}

function slowPromise<T>(identifier: string, timeout: number, result: T): Promise<T> {
  return new Promise((resolve) => {
    console.log(`Starting promise: ${identifier}`)
    setTimeout(() => {console.log(`Ending promise: ${identifier}`) resolve(result)}, timeout)
  })
}

Calling awaitTestSequence yields output like:

Starting promise: 1
Ending promise: 1
Starting promise: 2
Ending promise: 2

Yet, promise 2 doesn't need anything from promise 1, why wait at all to execute it?

Sure, you can avoid unnecessary waiting in native TS, but the best you can come up with is something like:

async function betterAwaitTestSequence() {
  let [result1, result2] = await Promise.all([
    slowPromise("1", 1000, 10),
    slowPromise("2", 1000, 10)
  ])

  return result1 + result2
}

This is already a little cumbersome even in the simplest case. You lose the ease of having a simple reference to the promise, unless you manually wrangle indices or use a destructuring approach. If you add a promise, you'll need to update to destructure that instance. Readability is hurt, as you have to mentally associate which promise is which index. In short, order matters here, but it shouldn't.

Further, Promise.all has a limitation on supporting only 10 promises before type information is lost.

Finally, let's imagine you wanted to use result1 or result2 to trigger further asynchronous work. This requires even more wrangling.

Instead, HigherOrderPromise provides a different abstraction. It lets you build objects which contain asynchronous properties, then compose operations on top of them.

Here's an example of the above using Await

HigherOrderPromise.from({
  result1: slowPromise("1", 1000, 10),
  result2: slowPromise("2", 1000, 10)
}).then((data) => {
  return data.result1 + data.result2
}).yield()

About

Balance readability and performance for async operations

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •