go-async

1.1.0 • Public • Published

go-async

Generator based async/await code blocks with support for abortion, form of TCO and fast synchronous execution.

Fast synchronous execution

Sometimes potentially async values are more often immediately available than not. Typical examples of such cases are caching and lazy initialisation.

Standard control flow utilities wrap and defer synchronous values what leads to a very pure performance (100, 1000, 1000000+ times slower than analogous synchronous code).

To the contrary, go-async's yield statement can accept synchronous values directly. In addition the Future object is provided which is analogous to Promise, but can be completed and queried synchronously.

The overall result can be summarised with benchmark below.

suite.add('Sync iterating 100 element array', function() {
  var future = go(function*() {
    var sum = 0
    for (var i = 0; i < array_100.length; i++) {
      sum += yield array_100[i]
    }
    return sum
  })
  assert.equal(future.value, 100)
}) // x 283,427 ops/sec ±2.06% (83 runs sampled)
 
suite.add('Plain 100 element array iteration via for loop', function() {
  var sum = 0
  for (var i = 0; i < array_100.length; i++) {
    sum += array_100[i]
  }
  assert.equal(sum, 100)
}) // x 6,753,023 ops/sec ±1.11% (91 runs sampled)

As we can async case is still ~ 30 times slower, but that better than millions.

Abortion

Async computations can be aborted. This is done by raising a special abort exception (e.go_abort_exception == true) on a current yield statement.

var future = go(function* copy() {
  try {
    var src = yield open('foo/src')
    var target = yield open('foo/target', 'w')
    var chunk
    while(null != (chunk = yield src.read())) {
      yield target.write(chunk)
    }
  } finally {
    src && src.close()
    target && target.close()
  }
})
 
// abort coping if it takes more than 10 secs
setTimeout(function() {
  future.abort()
}, 10000)

Tail calls

go(function* process() {
  var resource = open()
  try {
    yield processResource(resource)
    return anotherProcessingMode()
  } finally {
    resource.close()
  }
}).get(function(err, result) {
  assert.equal(result, 1)
})
 
function* anotherProcessingMode() {
  // By the time execution reaches this line `resource.close()` will be called.
  return 1
}

Usage

const go = require('go-async')
 
let future = go(function*() {
  let v1 = yield Promise.resolve(1) // wait for promise
  let v2 = yield (function*() { return 1 })() // wait for another async code block (i.e. generator)
  let v3 = yield go.thunk(cb => cb(null, 1)) // wait for thunk
  let v4 = yield go.run(1) // wait for go.Future
  let v5 = yield 1 // immediately proceed with the given value
  return v1 + v2 + v3 + v4 + v5
})
 
// Query for result
if (future.ready) {
  assert.equal(future.value, 5)
  assert.equal(future.error, null)
}
 
// Get the result via node style callback. It might be called immediately.
future.get(function(err, val) {
  assert.equal(err, null)
  assert.equal(val, 5)
})
 
// Convert to Promise
assert(future.toPromise() instanceof Promise)
 
// Use Promise methods directly on future
future.then(val => console.log(val))

Async value protocol

go-async recognises async values by the presence of special protocol methods. We went with this approach because it is somewhat faster than duck typing. The downside is that we ought to patch some standard prototypes.

Currently there are 3 types of async values

  • Generator (i.e all generators are treated as an async code blocks)
  • Promise
  • go.Future

You can normalize all values (sync and async) to go.Future with go.run().

Patching Promise.prototype

If you use non-standard Promise implementation, make sure you patched it's prototype with go.patchPromise().

Installation

Via npm

npm install go-async

License

MIT

Readme

Keywords

Package Sidebar

Install

npm i go-async

Weekly Downloads

4

Version

1.1.0

License

MIT

Unpacked Size

11.9 kB

Total Files

3

Last publish

Collaborators

  • eldar