Async Rosetta Stone
When we say "Effection is Structured Concurrency and Effects for JavaScript", we mean "JavaScript" seriously. You shouldn't have to learn an entirely new way of programming just to achieve structured concurrency. That's why the Effection APIs mirror ordinary JavaScript APIs so closely. That way, if you know how to do it in JavaScript, you know how to do it in Effection.
The congruence between vanilla JavaScript constructs and their Effection counterparts is reflected in the “Async Rosetta Stone.”
Async/Await | Effection |
---|---|
await | yield* |
async function | function* |
Promise | Operation |
new Promise() | action() |
for await | for yield* each |
AsyncIterable | Stream |
AsyncIterator | Subscription |
await
<=> yield*
Pause a computation and resume it when the value represented by the right hand side becomes available.
Continue once a promise has settled:
await promise;
Continue when operation is complete.
yield* operation;
async function
<=> function*
Compose a set of computations together with logic defined by JavaScript syntax:
Count down from 5 to 1 with an async function:
async function countdown() {
for (let i = 5; i > 1; i--) {
console.log(`${i}`);
await sleep(1000);
}
console.log('blastoff!');
}
Count down from 5 to 1 with a generator function:
import { sleep } from 'effection';
function* countdown() {
for (let i = 5; i > 1; i--) {
console.log(`${i}`);
yield* sleep(1000);
}
console.log('blastoff!');
}
Both will print:
5
4
3
2
1
blastoff!
To call an async function within an operation use call()
:
import { call } from 'effection';
yield* call(async function() {
return "hello world";
});
To run an operation from an async function use run()
or Scope.run
:
import { run } from 'effection';
await run(function*() {
return "hello world";
});
Promise
<=> Operation
The Promise
type serves roughly the same purpose as the Operation
. It is a
abstract value that you can use to pause a computation, and resume when the
value has been computed.
To use a promise:
let promise = await promise;
To use an operation:
let operation = yield* operation;
To convert from a promise to an operation, use call()
import { call } from 'effection';
let operation = call(promise);
to convert from an operation to a promise, use run()
or Scope.run
import { run } from 'effection';
let promise = run(operation);
new Promise()
<=> action()
Construct a reference to a computation that can be resolved with a callback.
In the case of Promise()
the value will resolve in the next tick of the run
loop.
Create a promise that resolves in ten seconds:
new Promise((resolve) => { setTimeout(resolve, 10000) });
Create an Operation that resolves in ten seconds:
import { action } from 'effection';
action(function*(resolve) { setTimeout(resolve, 10000) });
A key difference is that the promise body will be executing immediately, but the action body is only executed when the action is evaluated. Also, it is executed anew every time the action is evaluated.
for await
<=> for yield* each
Loop over an AsyncIterable with for await
:
for await (let item of iterable) {
//item logic
}
Loop over a Stream
with for yield* each
import { each } from 'effection';
for (let item of yield* each(stream)) {
// item logic
yield* each.next();
}
See the definition of each()
for more detail.
AsyncIterable
<=> Stream
A recipe for instantiating a sequence of items that can arrive over time. It is not the sequence itself, just how to create it.
Use an AsyncIterable
to create an AsyncIterator
:
let iterator = asyncIterable[Symbol.asyncIterator]();
Use a Stream
to create a Subscription
:
let subscription = yield* stream;
To convert an AsyncIterable
to a Stream
use the stream()
function.
import { stream } from 'effection';
let itemStream = stream(asyncIterable);
AsyncIterator
<=> Subscription
A stateful sequence of items that can be evaluated one at a time.
Access the next item in an async iterator:
let next = await iterator.next();
if (next.done) {
return next.value;
} else {
console.log(next.value)
}
Access the next item in a subscription:
let next = yield* subscription.next();
if (next.done) {
return next.value;
} else {
console.log(next.value);
}
To convert an AsyncIterator
to a Subscription
, use the
subscribe()
function.
let subscription = subscribe(asyncIterator);