waitless-async-benchmarks

0.3.0 • Public • Published

waitless-async-benchmarks

A series of benchmarks comparing the performance of different asynchronous processing libraries and styles in JavaScript.

Sequential pipe

This benchmark tests a series of asynchronous operations (or functional steps) whereby the output result of each step is input to a next step in sequential fashion. Our test case is a simple arithmetic calculation that evaluates 2**5 over 5 steps using the Node's setImmediate function and asserts that the result is 32.

Implementations of this benchmark in standard JavaScript idioms (including the infamous "pyramid of doom" or "callback hell" style, promises & async/await) and JS libraries are compared in a Node.js v10.15.2 test environment running on a 4-core Linux box.

Tests using JS nested callback style as a standard reference

Background reading and references

Test(s)
A, B, C Introducing async JavaScript draft on MDN
D async npm package - waterfall function
E A functional compose/reduce pipe method popularised by Eric Elliot in numerous tweets on Twitter
F waitless npm package - pipe function using waitless' Immediate or Promise strategy with accurate static typing
G waitless npm package - pipe function using wailess' Continuation Passing Style strategy with accurate static typing

TypeScript code: sequential pipe benchmark

import * as assert from 'assert';
import * as asyn from 'async';
import * as bench from 'benchmark';
import * as waitless from 'waitless';

/** Just for the benefit of the *Benchmark.js* benchmarking package. */
type Deferred = { resolve(): void };

/** Creates a Promise that is resolved by Node's `setImmediate` function. */
function setImmediatePromise<T>(arg?: T): Promise<T> {
  return new Promise((resolve) => setImmediate((arg?: T) => resolve(arg), arg));
}

/**
 * $_JS_nested_callback_style
 *
 * Asynchronous pipe implemented in classic JavaScript nested callback function
 * style (aka "pyramid of doom" or "callback hell").
 *
 * @param {Deferred} deferred - needed by Benchmark.js to signal when the async
 *  test is complete
 */
function $_JS_nested_callback_style(deferred: Deferred) {
  setImmediate(
    (a) =>
      setImmediate(
        (b) =>
          setImmediate(
            (c) =>
              setImmediate(
                (d) =>
                  setImmediate(
                    (e) =>
                      setImmediate((f) => {
                        assert.equal(f, 32);
                        deferred.resolve();
                      }, e * 2),
                    d * 2
                  ),
                c * 2
              ),
            b * 2
          ),
        a * 2
      ),
    1
  );
}

/**
 * A_ES2015_promise_then_chain
 *
 * Asynchronous pipe implemented by way of an ES2015 promise.then chain.
 *
 * @param {Deferred} deferred - needed by Benchmark.js to signal when the async
 *  test is complete
 */
function A_ES2015_promise_then_chain(deferred: Deferred) {
  let prom = setImmediatePromise(1)
    .then((a) => setImmediatePromise(a * 2))
    .then((b) => setImmediatePromise(b * 2))
    .then((c) => setImmediatePromise(c * 2))
    .then((d) => setImmediatePromise(d * 2))
    .then((e) => setImmediatePromise(e * 2));

  prom.then((res) => {
    assert.equal(res, 32);
    deferred.resolve();
  });
}

/**
 * B_ES2017_async_with_inline_await
 *
 * Asynchronous pipe implemented as an ES2017 async function with inline await
 * statements.
 *
 * @param {Deferred} deferred - needed by Benchmark.js to signal when the async
 *  test is complete
 */
async function B_ES2017_async_with_inline_await(deferred: Deferred) {
  let a = await setImmediatePromise(1);
  let b = await setImmediatePromise(a * 2);
  let c = await setImmediatePromise(b * 2);
  let d = await setImmediatePromise(c * 2);
  let e = await setImmediatePromise(d * 2);
  let f = await setImmediatePromise(e * 2);
  assert.equal(f, 32);
  deferred.resolve();
}

/**
 * C_async_library_waterfall_function
 *
 * Asynchronous pipe implemented using the `waterfall` function from the *async*
 * npm package.
 *
 * @param {Deferred} deferred - needed by Benchmark.js to signal when the async
 *  test is complete
 */
function C_async_library_waterfall_function(deferred: Deferred) {
  asyn.waterfall(
    [
      (callback: any) => setImmediate(() => callback(null, 1)),
      (a: any, callback: any) => setImmediate(() => callback(null, a * 2)),
      (b: any, callback: any) => setImmediate(() => callback(null, b * 2)),
      (c: any, callback: any) => setImmediate(() => callback(null, c * 2)),
      (d: any, callback: any) => setImmediate(() => callback(null, d * 2)),
      (e: any, callback: any) => setImmediate(() => callback(null, e * 2))
    ],
    (err, f: any) => {
      assert.equal(f, 32);
      deferred.resolve();
    }
  );
}

/**
 * D_Async_pipe_by_functional_reduction
 *
 * Asynchronous pipe implemented using Eric Elliot's async pipe by functional
 * reduction/composition.
 *
 * @param {Deferred} deferred - needed by Benchmark.js to signal when the async
 *  test is complete
 *
 * @see https://twitter.com/_ericelliott/status/895791749334945792
 */
const EricElliot_async_pipe = (...fns: any[]) => (x: any) =>
  fns.reduce(async (y, f) => f(await y), x);

function D_Async_pipe_by_functional_reduction(deferred: Deferred) {
  let mypipe = EricElliot_async_pipe(
    (a: any) => setImmediatePromise(a * 2),
    (b: any) => setImmediatePromise(b * 2),
    (c: any) => setImmediatePromise(c * 2),
    (d: any) => setImmediatePromise(d * 2),
    (e: any) => setImmediatePromise(e * 2)
  );

  let prom = mypipe(1);

  prom.then((f: any) => {
    assert.equal(f, 32);
    deferred.resolve();
  });
}

/**
 * E_waitless_library_IOP_pipe
 *
 * Asynchronous pipe implemented using the `iop.pipe` function from the
 * *waitless* npm package.
 *
 * @param {Deferred} deferred - needed by Benchmark.js to signal when the async
 *  test is complete
 */
function E_waitless_library_IOP_pipe(deferred: Deferred) {
  let mypipe = waitless.iop.pipe(
    (a: number) => setImmediatePromise(a * 2),
    (b) => setImmediatePromise(b * 2),
    (c) => setImmediatePromise(c * 2),
    (d) => setImmediatePromise(d * 2),
    (e) => setImmediatePromise(e * 2)
  );

  let iop = mypipe(1);

  Promise.resolve(iop).then((f) => {
    assert.equal(f, 32);
    deferred.resolve();
  });
}

/**
 * F_waitless_library_CPS_pipe
 *
 * Asynchronous pipe implemented using the `cps.pipe` function from the
 * *waitless* npm package.
 *
 * @param {Deferred} deferred - needed by Benchmark.js to signal when the async
 *  test is complete
 */
function F_waitless_library_CPS_pipe(deferred: Deferred) {
  let mypipe = waitless.cps.pipe(
    (ret: Tv.Async.Retrn<number>, a: number) => setImmediate(ret, a * 2),
    (ret: Tv.Async.Retrn<number>, b) => setImmediate(ret, b * 2),
    (ret: Tv.Async.Retrn<number>, c) => setImmediate(ret, c * 2),
    (ret: Tv.Async.Retrn<number>, d) => setImmediate(ret, d * 2),
    (ret: Tv.Async.Retrn<number>, e) => setImmediate(ret, e * 2)
  );

  mypipe((f) => {
    assert.equal(f, 32);
    deferred.resolve();
  }, 1);
}

Sequential cascade

TODO description

Tests using async await as a standard reference

Running the benchmarks from the command-line

$ npx waitless-async-benchmarks

Requires:

  1. Node.js version 10.x.x or higher
  2. npx version 6.4.0 or higher
$ npx waitless-async-benchmarks

npx: installed 7 in 4.727s
Starting waitless-async-benchmarks (2 suites)

Running async_pipe suite (1 of 2)

$_JS_nested_callback_style x 27,365 ops/sec ±1.44% (78 runs sampled)
A_ES2015_promise_then_chain x 21,404 ops/sec ±1.46% (78 runs sampled)
B_ES2017_async_with_inline_await x 19,435 ops/sec ±1.05% (80 runs sampled)
C_async_library_waterfall_function x 18,908 ops/sec ±0.81% (79 runs sampled)
D_Async_pipe_by_functional_reduction x 19,621 ops/sec ±1.37% (64 runs sampled)
E_waitless_library_IOP_pipe x 20,931 ops/sec ±1.20% (77 runs sampled)
F_waitless_library_CPS_pipe x 25,794 ops/sec ±0.76% (80 runs sampled)

Running async_cascade suite (2 of 2)

$_ES2017_async_await x 5,978 ops/sec ±3.83% (69 runs sampled)
A_waitless_library_IOP_cascade x 5,915 ops/sec ±1.26% (73 runs sampled)
B_waitless_library_CPS_cascade x 6,788 ops/sec ±3.40% (69 runs sampled)

$

License

This software is licensed MIT.

Copyright © 2019 Justin Johansson

Package Sidebar

Install

npm i waitless-async-benchmarks

Weekly Downloads

1

Version

0.3.0

License

MIT

Unpacked Size

72.6 kB

Total Files

19

Last publish

Collaborators

  • typeversity