Asynchronous code (PromiseV2 / async / await)

PromiseV2

In Opal 1.2 we introduced PromiseV2 which is to replace the default Promise in Opal 2.0 (which will become PromiseV1). Right now it's experimental, but the interface of PromiseV1 stay unchanged and will continue to be supported.

It is imperative that during the transition period you either require 'promise/v1' or require 'promise/v2' and then use either PromiseV1 or PromiseV2.

If you write library code it's imperative that you don't require the promise itself, but detect if PromiseV2 is defined and use the newer implementation, for instance using the following code:

module MyLibrary
  Promise = defined?(PromiseV2) ? PromiseV2 : ::Promise
end

The difference between PromiseV1 and PromiseV2 is that PromiseV1 is a pure-Ruby implementation of a Promise, while PromiseV2 is reusing the JavaScript Promise. Both are incompatible with each other, but PromiseV2 can be awaited (see below) and they translate 1 to 1 to the JavaScript native Promise (they are bridged; you can directly return a Promise from JavaScript API without a need to translate it). The other difference is that PromiseV2 always runs a #then block a tick later, while PromiseV1 would could run it instantaneously.

Async/await

In Opal 1.3 we implemented the CoffeeScript pattern of async/await. As of now, it's hidden behind a magic comment, but this behavior may change in the future.

Example:

# await: true

require "await"

def wait_5_seconds
  puts "Let's wait 5 seconds..."
  sleep(5).__await__
  puts "Done!"
end

wait_5_seconds.__await__

It's important to understand what happens under the hood: every scope in which #__await__ is encountered will become async, which means that it will return a PromiseV2 that will resolve to a value. This includes methods, blocks and the top scope. This means, that #__await__ is infectious and you need to remember to #__await__ everything along the way, otherwise a program will finish too early and the values may be incorrect.

It is certainly correct to #__await__ any value, including non-Promises, for instance 5.__await__ will correctly resolve to 5 (except that it will make the scope an async function, with all the limitations described above).

The await stdlib module includes a few useful functions, like async-aware each_await function and sleep that doesn't block the thread. It also includes a method #await which is an alias of #itself - it makes sense to auto-await that method.

You can take a look at how we ported Minitest to support asynchronous tests..

This approach is certainly incompatible with what Ruby does, but due to a dynamic nature of Ruby and a different model of JavaScript this was the least invasive way to catch up with the latest JavaScript trends and support Promise heavy APIs and asynchronous code.

Auto-await

The magic comment also accepts a comma-separated list of methods to be automatically awaited. An individual value can contain a wildcard character *. For instance, those two blocks of code are equivalent:

# await: true

require "await"

[1,2,3].each_await do |i|
  p i
  sleep(i).__await__
end.__await__
# await: sleep, *await*

require "await"

[1,2,3].each_await do |i|
  p i
  sleep i
end