Skip to main content

Tasks and Operations

In the introduction, we saw how we can replace async/await with Effection. Let's break down a bit further what is going on!

When you have an async function, and you call this function, you get back a Promise. When you call a Generator function you get back a Generator. This is slightly different in that a Generator needs to be iterated in order to actually do anything, while a Promise will make progress on its own.

If you run the following, it will print Hello World! to the console:

async function sayHello() {
console.log("Hello World!");
};

sayHello();

But this will not print anything:

function *sayHello() {
console.log("Hello World!");
};

sayHello();

Tasks

We need something to "drive" the iteration of the Generator, and in Effection this is a Task. Tasks have other responsibilities as well, as we'll see when we talk about spawn.

We can use the run function from Effection which takes a generator function and returns a Task.

import { run } from 'effection';

run(function*() {
console.log("Hello World!");
});

You should see this print Hello World! to the console, as you'd expect.

The return value we get from run is a Task. A Task can act like a Promise, so we can use it with await, to integrate Effection into existing async/await code, and we can also use catch to catch any errors the occurred in the task. There are also some other things that tasks can do, for example they can spawn other tasks.

import { run } from 'effection';

let task = run(function*() {
throw new Error('oh no!');
});

task.catch((error) => {
console.error(error);
});

console.log('running task', task.id) // tasks have a unique ID

We have already met the function main, which is very similar to run. main takes care of a few things for you, like cleaning up when the process shuts down, and printing errors to the console in case anything goes wrong. If you're writing a program with Effection from scratch, you should normally use main as the entry point for your program. run is a bit more low-level and can be useful when you're integrating Effection into an existing codebase.

Using main our example looks like this:

import { main } from 'effection';

main(function*() {
throw new Error('oh no!');
});

You should see this print an error with nice formatting.

Operations

As we saw, we can pass a generator function as an argument to run and main, but these functions are actually a bit more general than that. In Effection we call the type of the argument an Operation. An Operation can be any of:

Additionally you can also call run and main without any argument, this will suspend indefinitely.

Yield

We have seen that we can use yield inside of a generator function to call other generator functions. In fact, we can use yield to call any other operation!

For example we can use yield to wait for a Promise to resolve:

import { main } from 'effection';

main(function*() {
let text = yield Promise.resolve("Hello World!");
console.log(text);
});

We can also use yield with another generator function:

import { main, sleep } from 'effection';

main(function*() {
let text = yield function*() {
yield sleep(1000);
return "Hello World!";
}
console.log(text);
});

Or with a generator:

import { main, sleep } from 'effection';

function* makeSlow(value) {
yield sleep(1000);
return value;
}

main(function*() {
let text = yield makeSlow('Hello World!');
console.log(text);
});

Finally, if we use yield without an argument, it will suspend indefinitely:

import { main } from 'effection';

main(function*() {
yield;
console.log('We will never get here!');
});