Class in Sqimitive Async

class Sqimitive.Async extends Sqimitive.Base
Asynchronous operation manager – a “promise” on steroids, with intuitive grouped handling of a large number of asynchron

Defined in: async.js, line 43

Description (skip)

Asynchronous operation manager – a “promise” on steroids, with intuitive grouped handling of a large number of asynchronous processes.

Async is “native” Sqimitive’s re-implementation of JavaScript’s Promise. It provides a unified wrapper around asynchronous operations such as remote requests. Unlike Promise, Async may contain nest’ed operations (the parent only completes when all children complete, recursively), allows the usual (evt) event listeners and can be abort()’ed.

Async’s state is stored in the status _opt’ion (true when succeeded, false if failed, null if still incomplete). On change, Async fires success or error and, always, complete:

Attention: Base.nest() returns new child, not the parent. The following creates an Async containing an Async which in turn has another Async inside, to which whenSuccess() is attached:

;(new Sqimitive.Async)
  .nest(new Sqimitive.Async)
  .nest(new Sqimitive.Async)
  .whenSuccess(...)

This most likely indicates an error and should be rewritten like so:

var container = new Sqimitive.Async)
container.nest(new Sqimitive.Async)
container.nest(new Sqimitive.Async)
container.whenSuccess(...)

Nesting like that can be shortened if Sqimitive.Async is the default class (Base._childClass) for container (as it is by default):

container.nest({})

Other notes:

ExampleA subclass that wraps around JavaScript’s Image (representing the <img> tag) for preloading graphical resources.
var AsyncImage = Sqimitive.Async.extend({
  _opt: {
    src: null,   // relative to current location
    img: null,   // JavaScript's Image
  },

  events: {
    init: function () {
      var img = new Image
      img.onload  = () => this.set('status', true)
      img.onerror = () => this.set('status', false)
      this.set('img', img)
      img.src = this.get('src')
    },

    // Stops image loading (if supported by the browser).
    abort: function () {
      this.img() && (this.img().src = 'javascript:void 0')
    },
  },

  img: function () {
    return this.get('img')
  },

  width: function () {
    return this.img() && this.img().width
  },

  height: function () {
    return this.img() && this.img().height
  },
})
Using AsyncImage to load a single image:
;(new AsyncImage({src: 'pic.jpg'})
  .whenSuccess(async => alert(async.width()))

// Similar to:
var img = new Image
img.src = 'pic.jpg'
img.onload = () => alert(img.width)
Loading multiple AsyncImage-s:
var container = new Sqimitive.Async
container.nest(new AsyncImage({src: 'foo.png'}))
container.nest(new AsyncImage({src: 'bar.bmp'}))
container.whenSuccess(function () {
  alert('Loaded ' + container.invoke('get', 'src').join())
})
container.whenError(function () {
  // Since Async is the usual Sqimitive.Base, we have all the utility
  // methods:
  var failed = this.reject(a => a.isSuccessful())
  alert('Failed to load: ' + failed.map(a => a.get('src')).join())
})
The above container in turn could be part of another Async:
var resourcePreloader = new Sqimitive.Async
resourcePreloader.nest(imagePreloader)
resourcePreloader.nest(dataPreloader)
resourcePreloader.whenSuccess(() => game.start())
…With dataPreloader defined “ad-hoc” (without a specialized class):
var dataPreloader = new Sqimitive.Async

$.loadJSON('map.json', function (data) {
  dataPreloader.set('data', data)
  dataPreloader.set('status', true)
})

dataPreloader.whenSuccess(function () {
  var data = dataPreloader.get('data')
  // ...
})

Event order and nesting

Often you want to call when...() on a child being nest()’ed, such as when wrapping an Async. In this example API calls are delayed but the caller receives an Async immediately. It might be tempting to write something like this:

var queue = []
var timer

function callAPI(args) {
  var result = new Sqimitive.Async
  queue.push([args, result])

  timer = timer || setTimeout(function () {
    timer = null

    for (var task; task = queue.shift(); ) {
      task[1].nest(_callAPI(task[0]))
        .whenSuccess(function () { task[1].result = this.result })
    }
  }, 100)

  return result
}

function _callAPI(args) {
  var internal = new Sqimitive.Async
  _.ajax({
    url: '/api',
    data: _.toArray(args),
    success: function (xhr) {
      internal.result = xhr.response
      internal.set('status', true)
    },
  })
  return internal
}

The Async returned to the caller is nested an internal Async object returned by _callAPI(); when the latter succeeds or fails, the caller’s Async automatically finishes and callAPI() copies response data (result) to the wrapper – but running into the same pitfall described in _opt.ignoreError, namely that whenSuccess() is called after nesting, allowing the caller’s hooks to execute first.

// wrapper is the result Async in callAPI().
var wrapper = callAPI('foo')
wrapper.whenSuccess(function () { console.log(this.result) })
  // logs undefined because wrapper succeeds (internal Async is nested)
  // before callAPI()'s whenSuccess() runs

Constants

MAX_PRIORITY

The maximum number (absolute, i.e. positive) accepted by whenComplete() and others; out of range priority is clamped to the nearest value.

ExampleUse -Infinity and Infinity to specify minimum and maximum priority.
// This function is called before listeners to 'success' with a larger
// priority (which is 0 by default).
async.whenSuccess(function (async) { ... }, this, -Infinity)

Defined in: async.js, line 266Show code

MAX_PRIORITY: 2,

Properties

_opt

Modifiers: protected

New standard options (Base._opt):

Members
Name Types Notes
statusoperation null not started or not finished Reflects both this instance’s own status and status of its children.

When an operation wrapped by this Async finishes and assuming it has no _children, set() status to either true (isSuccessful) or false. This instance will stop being isLoading(); this should be a permanent condition – don’t change status once it’s becomes non-null, until you call clear().

true operation succeeded
false operation failed
ignoreErrorbool If set, this instance will be considered succeeded by isSuccessful() even if status is false

Don’t change this value when this instance is no longer isLoading() because the expected handlers won’t be called:

async.set('status', false)
async.whenSuccess(...)
async.set('ignoreError', true)
  // whenSuccess' func is not executed despire isSuccessful()
  // returning true

// Best practice is to give ignoreError to the constructor:
var async = new Sqimitive.Async({ignoreError: true})
// ...or to extend():
var MyAsync = Sqimitive.Async.extend({
  _opt: {ignoreError: true},
})

Remember that nesting an already failed Async fails the parent immediately if there are no other isLoading children or if immediateError is set. Change ignoreError before nesting:

var parent = new Sqimitive.Async
parent.nest( new Sqimitive.Async({status: false}) )
  .set('ignoreError', true)
// parent is !isSuccessful().
parent.nest( (new Sqimitive.Async({status: false}))
  .set('ignoreError', true) )
// parent is isSuccessful(); observe the braces.

immediateErrorbool If set, this instance will be considered failed as soon as any child fails, even if there is an isLoading() child.

This value affects the next child status check so it’s best changed when there are no children.

The default (disabled) is safe because it ensures completion of all children before completing the parent, avoiding any orphan processes. But it is often wasteful to wait when you already know the process in general has failed. Enabling immediateError helps but it becomes your job to abort() or clear() all sub-tasks, and to ignore or otherwise handle events fired on sub-tasks after completion of the parent with immediateError.

var parent = new Sqimitive.Async
parent.whenError(func)
var loading = parent.nest(new Sqimitive.Async)
  // parent.isLoading() == true
loading.whenComplete(func2)
parent.nest(new Sqimitive.Async({status: false}))
  // parent.isLoading() == true
loading.set('status', true)
  // func2, then func called; loading.isLoading() == false

var parent = new Sqimitive.Async({immediateError: true})
parent.whenError(func)
var loading = parent.nest(new Sqimitive.Async)
  // parent.isLoading() == true
loading.whenComplete(func2)
loading.on('abort', () => loading.set('status', false))
parent.nest(new Sqimitive.Async({status: false}))
  // func called; loading.isLoading() == true
parent.clear()
  // func2 called; loading.isLoading() == false

Defined in: async.js, lines 351-355 (5 lines) • Show code

_opt: {
  status: null,
  ignoreError: false,
  immediateError: false,
},

Methods

abort ( )

Aborts the operation of this instance.

Exampleabort() doesn’t affect own _children – call sink() or clear() to abort everything recursively:
async.sink('abort')

The default implementation does nothing (is a stub).

Defined in: async.js, line 781

clear ( )

Prepares this instance for new use.

It’s recommended to create a new Async instance for every new batch of tasks to avoid induced effects. However, clear() may improve performance when thousands of objects need to be created but some could be reused. clear() can be also seen as “hard” abort().

Removes event listeners for success/error/complete (but not for exception), calls abort() on self and on _children (recursively), calls Base.remove() on children if _owning or unlist() if not, and sets status (_opt) to null (isLoading()).

Defined in: async.js, lines 609-615 (7 lines) • Show code

clear: function () {
  _.forEach(['success', 'error', 'complete'], function (e) { this.off(e) }, this)
  this.sink('abort', [], true)
  this.forEach(this.unlist)
  this.set('status', null)
  return this
},
complete ( this )

Called when this instance stops being isLoading().

from sec

success, error and complete are called when this and nested operations have finished (resources loaded, etc.). Consider this analogy with exception handling:

try {
  ...                    // Async.success()
} catch (exception) {
  ...                    // Async.error()
} finally {
  ...                    // Async.complete()
}

But note that “real” exceptions thrown by listeners are handled by exception() outside of the normal pipeline (error-s are expected conditions, exceptions are not).

Each handler is called at most once as if it was bound with once(). If a handler changes the status _opt, others are skipped and events are re-fired.

ExampleIn most cases you need to use whenSuccess(), whenError() and whenComplete() rather than subscribing via on() because if the instance’s status changes between its construction and your subscription - your handler will not be called:
var async = new MyAsync({...})
// If async is already complete before we call on() - our handler will
// not be triggered:
async.on('success', function (async) { ... })
// So you want to do this:
async.whenSuccess(function (async) { ... })

The call order of handlers is deterministic (Async’s nest() acts as on() because of _childEvents/_forward()) but it’s usually more convenient to use specific priority levels to not depend on the order of nest()/on() calls: on('success') is guaranteed to be called after success-3 but before success2.

Defined in: async.js, line 736

doneIfEmpty ( )

Sets the status _opt to true if have no _children and is still isLoading().

Result Types
Types Notes
undefined if not empty or is not loading
true
Example
_.each(filesToLoad, function (url) {
  async.nest(new MyAsync({src: url}))
})

async.whenComplete(allLoaded)

async.doneIfEmpty()
  //=> true
  // if there were no filesToLoad then mark async as complete right
  // away

async.doneIfEmpty()
  //=> undefined - already complete, call ignored

Defined in: async.js, lines 591-596 (6 lines) • Show code

doneIfEmpty: function () {
  if (!this.length && this.isLoading()) {
    this.set('status', true)
    return true
  }
},
error ( this )

Called when this instance or one of its children has failed.

By default, Async doesn’t store any error information because it is operation-agnostic; you may use _opt for this.

from sec

success, error and complete are called when this and nested operations have finished (resources loaded, etc.). Consider this analogy with exception handling:

try {
  ...                    // Async.success()
} catch (exception) {
  ...                    // Async.error()
} finally {
  ...                    // Async.complete()
}

But note that “real” exceptions thrown by listeners are handled by exception() outside of the normal pipeline (error-s are expected conditions, exceptions are not).

Each handler is called at most once as if it was bound with once(). If a handler changes the status _opt, others are skipped and events are re-fired.

ExampleIn most cases you need to use whenSuccess(), whenError() and whenComplete() rather than subscribing via on() because if the instance’s status changes between its construction and your subscription - your handler will not be called:
var async = new MyAsync({...})
// If async is already complete before we call on() - our handler will
// not be triggered:
async.on('success', function (async) { ... })
// So you want to do this:
async.whenSuccess(function (async) { ... })

The call order of handlers is deterministic (Async’s nest() acts as on() because of _childEvents/_forward()) but it’s usually more convenient to use specific priority levels to not depend on the order of nest()/on() calls: on('success') is guaranteed to be called after success-3 but before success2.

Defined in: async.js, line 726

exception ( e )

Called when an exception was thrown during success, error or complete.

Example
async.on('exception', e => console.error(e.message))

In contrast with JavaScript’s Promise an error in handling the “promise” (Async) doesn’t mark that promise failed if its underlying action (e.g. an AJAX call) has succeeded.

If an exception is thrown during an error or success handlers then remaining handlers are skipped, complete is triggered and the exception is re-thrown. An exception during complete also skips remaining handlers of complete and is re-thrown unless there was an exception during error or success.

Other notes:

  • exception’s event listeners are not affected by clear().
  • Async replaces the exception() handler of its children so that the exception is thrown on the top-level Async (whose _parent is not another Async).
  • The above also means that it’s enough to override just the top-level exception() to affect the entire tree.
  • The usual events have “whenXXX()” (whenComplete(), etc.) but there’s no “whenException()” because no info about already occurred errors is stored (like status is stored in _opt). Only future exceptions can be listened to, with the usual on().
  • Unlike success/error/complete, exception doesn’t receive this as an argument.
  • exception isn’t called on request failure unless you throw an exception from error (because Async doesn’t know about your operation’s details).
  • The default implementation throws the argument.

Defined in: async.js, lines 777-779 (3 lines) • Show code

exception: function (e) {
  throw e
},
isLoading ( )
Result Types
Types Notes
true when still loading (status _opt is null)
false when failed or succeeded (isSuccessful)

Defined in: async.js, lines 676-678 (3 lines) • Show code

isLoading: function () {
  return this.get('status') == null
},
isSuccessful ( )
Result Types
Types Notes
null when still isLoading()
true if status _opt is true or ignoreError _opt is set
false

ignoreError affects all methods using isSuccessful(), in particular whenSuccess() and whenError():

async.set('status', false)   // fail it
async.isSuccessful()         //=> false
async.set('ignoreError', true)
async.isSuccessful()         //=> true
async.whenSuccess(() => alert('fired!'))
  // alerts

However, changing ignoreError on run-time is not recommended – see _opt.

Defined in: async.js, lines 669-672 (4 lines) • Show code

isSuccessful: function () {
  var s = this.get('status')
  return s == null ? null : !!(this.get('ignoreError') || s)
},
nestDoner ( )

Nests a new child and returns a function that sets the child’s status to true.

The returned function has an error method that sets the child’s status to false.

ExampleUse nestDoner() to interface with callback-style functions:
$('div').fadeOut(async.nestDoner())

var done = async.nestDoner()
fs.appendFile('foo.txt', 'Hello World!', function (err) {
  err ? done.error() : done()
})

async.whenSuccess(function () {
  var $ = require('jquery')
  // ...
})
var done = async.nestDoner()
require(['jquery'], done, done.error)

status is not changed after abort() was called on the child (this usually happens because of sink() or clear() on the parent since nestDoner() doesn’t return the child it creates).

Defined in: async.js, lines 643-651 (9 lines) • Show code

nestDoner: function () {
  var child = this.nest({})
  child.on('abort', function () { child = null })
  function nestDoner_(error) {
    child && child.set('status', error !== unique)
  }
  nestDoner_.error = function () { nestDoner_(unique) }
  return nestDoner_
},
success ( this )

Called when this instance and children have succeeded.

secsuccess, error and complete are called when this and nested operations have finished (resources loaded, etc.). Consider this analogy with exception handling:

try {
  ...                    // Async.success()
} catch (exception) {
  ...                    // Async.error()
} finally {
  ...                    // Async.complete()
}

But note that “real” exceptions thrown by listeners are handled by exception() outside of the normal pipeline (error-s are expected conditions, exceptions are not).

Each handler is called at most once as if it was bound with once(). If a handler changes the status _opt, others are skipped and events are re-fired.

ExampleIn most cases you need to use whenSuccess(), whenError() and whenComplete() rather than subscribing via on() because if the instance’s status changes between its construction and your subscription - your handler will not be called:
var async = new MyAsync({...})
// If async is already complete before we call on() - our handler will
// not be triggered:
async.on('success', function (async) { ... })
// So you want to do this:
async.whenSuccess(function (async) { ... })

The call order of handlers is deterministic (Async’s nest() acts as on() because of _childEvents/_forward()) but it’s usually more convenient to use specific priority levels to not depend on the order of nest()/on() calls: on('success') is guaranteed to be called after success-3 but before success2.

Defined in: async.js, line 680

toString ( )

Returns a debugger-friendly summary of this object:

The.Class.Name [length] ~¹ STATUS²

¹ ~ is output if the ignoreError _opt is set.

² STATUS is either “LOADING”, “DONE” or “ERROR”

For example:

AsyncImage [2]  DONE

Defined in: async.js, line 397

whenComplete ( func, cx, priority )

Fire func whenever this instance stops being isLoading() – see complete.

Example
$('#spinner').show()
var async = new MyAsync({...})
async.whenComplete(function (async) {
  $('#spinner').hide()
})

// As if:
$('#spinner').show()
try {
  do_async()
} finally {
  $('#spinner').hide()
}
Result Types
Types Notes
completeDescthis

Unlike with a simple on('event') (on()) func gets called immediately if the condition is already met (then priority is ignored). Otherwise, func is executed before all handlers registered with a larger priority, among (in any order) those with the same and after those with a lower. The value is clamped to the MAX_PRIORITY range. In any case, func is given this (the Async instance).

Warning: the following is incorrect use because func may be called immediately, before the result is assigned to req:

var req = (new Sqimitive.Async).whenComplete(function () {
  // WRONG: req may be undefined:
  req.foo()
})

// CORRECT: assign to the variable first:
var req = new Sqimitive.Async
req.whenComplete(function () { ... })

// CORRECT: or use the passed "this":
;(new Sqimitive.Async)
  .whenComplete(function (req) { ... })

Defined in: async.js, lines 542-544 (3 lines) • Show code

whenComplete: function (func, cx, priority) {
  return this._when(func, cx, priority, !this.isLoading(), 'complete')
},
whenError ( func, cx, priority )

Fire func whenever this instance’s operation has failed – see error.

Example
async.whenError(function (async) {
  alert("Met an error :'(")
})

from completeDesc

Result Types
Types Notes
this

Unlike with a simple on('event') (on()) func gets called immediately if the condition is already met (then priority is ignored). Otherwise, func is executed before all handlers registered with a larger priority, among (in any order) those with the same and after those with a lower. The value is clamped to the MAX_PRIORITY range. In any case, func is given this (the Async instance).

Warning: the following is incorrect use because func may be called immediately, before the result is assigned to req:

var req = (new Sqimitive.Async).whenComplete(function () {
  // WRONG: req may be undefined:
  req.foo()
})

// CORRECT: assign to the variable first:
var req = new Sqimitive.Async
req.whenComplete(function () { ... })

// CORRECT: or use the passed "this":
;(new Sqimitive.Async)
  .whenComplete(function (req) { ... })

Defined in: async.js, lines 555-557 (3 lines) • Show code

whenError: function (func, cx, priority) {
  return this._when(func, cx, priority, this.isSuccessful() == false, 'error')
},
whenSuccess ( func, cx, priority )

Fire func whenever this instance’s operation has succeeded – see success.

Example
async.whenSuccess(app.start)

from completeDesc

Result Types
Types Notes
this

Unlike with a simple on('event') (on()) func gets called immediately if the condition is already met (then priority is ignored). Otherwise, func is executed before all handlers registered with a larger priority, among (in any order) those with the same and after those with a lower. The value is clamped to the MAX_PRIORITY range. In any case, func is given this (the Async instance).

Warning: the following is incorrect use because func may be called immediately, before the result is assigned to req:

var req = (new Sqimitive.Async).whenComplete(function () {
  // WRONG: req may be undefined:
  req.foo()
})

// CORRECT: assign to the variable first:
var req = new Sqimitive.Async
req.whenComplete(function () { ... })

// CORRECT: or use the passed "this":
;(new Sqimitive.Async)
  .whenComplete(function (req) { ... })

Defined in: async.js, lines 567-569 (3 lines) • Show code

whenSuccess: function (func, cx, priority) {
  return this._when(func, cx, priority, this.isSuccessful() == true, 'success')
},