Replace callbacks with tasks internally

The callback system inside the interpreter required manually handling
errors at every asynchronous connection point. It was also prone to
errors when callbacks weren't called or called more than once. All of
the callbacks in the internals of the interpreter have been replaced
with 'tasks', modelled on the 'thenable' concept from JavaScript
promises. Tasks automatically pipe errors, and ensure that results can
only be processed once, while preserving the possibility of synchronous
behaviour. The external interface of the interpreter and the
asynchronous methods still use Node-style callbacks.