JS Task Executor Quickstart Explained
Introduction
This tutorial will utilize the same example used in the Quickstart and include thorough explanations.
Prerequisites
Yagna service is installed and running with the try_golem
app-key configured.
Setting up the project
Create a project folder, initialize a Node.js project, and install libraries.
mkdir golem-example
cd golem-example
npm init
npm i @golem-sdk/task-executor
npm i @golem-sdk/pino-logger
JS script structure
The basic structure of the script:
import { TaskExecutor } from '@golem-sdk/task-executor'
;(async () => {
//... Function body in here
})()
Here we do two things:
- import TaskExecutor from @golem-sdk/task-executor and
- create IIAFE (Immediately Invoked Async Function Expression). It has an async function declaration because TaskExecutor provides async methods.
Utilizing Task Executor instance
Inside the function body, there will be a sequence of 3 steps, that constitute the simplest Task Executor lifecycle. Task Executor is a primary object provided by our Task API.
(async () => {
// 1. Create a Task Executor Instance
const executor = await TaskExecutor.create({
...
});
try {
// 2. Run the task
const result = await executor.run();
} catch (err) {
console.error("An error occurred:", err);
} finally {
// 3. Close Task Executor
await executor.shutdown();
}
})();
In (1)
const executor = await TaskExecutor.create({
...
});
we create a TaskExecutor Instance using a factory method. Several parameters can be provided to define the environment where your tasks will be executed, the behavior of your requestor script, and the way it interacts with the Golem Network. They are explained later in this article.
Next in (2)
const result = await executor.run(
...
);
we run the task. Here we use a run
method that accepts a task function as its argument. We will define the task function in a moment. We store the result of the executor.run()
in the result
variable.
There are other ways to execute tasks. They are briefly presented in Task API Guide and explained in examples section.
Finally in (3) we gracefully finish the task executor:
await executor.shutdown()
Task Executor options
Let's look at the Task Executor parameters.
const executor = await TaskExecutor.create({
logger: pinoPrettyLogger({ level: "info" }),
api: { key: "try_golem" },
...
logger
- a logger instance - we use a special logger that produces logs easier to read and understand when scripts are run in the terminal, without this line a default logger would be used, which is the debug logger.api: {key: }
- a key that will give us access toyagna
REST API.yagna
is a service that connects us to the network. In this example, we will use api-key that was generated in the process of Yagna installation.
...
demand: {
workload: {
imageTag: "golem/node:20-alpine",
},
},
...
- In the
demand
section we define the environment needed to run our task. We do it by indicating what image is to be run on a remote node. We will use an image publicly available on the registry portal, therefore it is enough to provide a taggolem/node:20-alpine
- it indicates an image based onalpine
distribution and hasnode.js
installed. When defining the demand users can also specify other parameters like the minimal number of threads, memory, or disk size as needed.
...
market: {
rentHours: 0.5,
pricing: {
model: "linear",
maxStartPrice: 0.5,
maxCpuPerHourPrice: 1.0,
maxEnvPerHourPrice: 0.5,
},
}
Finally, we define market
parameters.
- The
rentHour
defines the maximum duration of the engagements with providers before automatic termination. - In
pricing
we also precise the maximum acceptable prices using thelinear
price model. It will be used to select offers from providers and let you manage the costs of running your tasks.
Defining task function
Let’s see how the task is defined and replace the ...
placeholder we used in the previous step.
The task is defined as a TaskFunction
. It is an async function that will get as its parameter an exeUnit
from the executor. The exeUnit
is an object that represents the actual environment on the provider and lets you run your commands within the scope of this one TaskFunction on one provider.
Our task in this example is simple and consists of a single command: namely node -v
. We will use the async method run()
of exeUnit exe
. The output of this method is a Promise
of a result
object, once it is resolved it contains the output of the command we run, available as a stdout
property.
Note your TaskFunction
may consist of multiple commands that allow the user to transfer his data to and from a provider. You can find more information on available commands in Examples.
;async (exe) => (await exe.run('node -v')).stdout
The output of the task function is passed to executor.run()
and assigned to task result result
. Finally, we print it to the console.
import { TaskExecutor } from "@golem-sdk/task-executor";
import { pinoPrettyLogger } from "@golem-sdk/pino-logger";
(async () => {
const executor = await TaskExecutor.create({
logger: pinoPrettyLogger({ level: "info" }),
api: { key: "try_golem" },
demand: {
workload: {
imageTag: "golem/node:20-alpine",
},
},
market: {
rentHours: 0.5,
pricing: {
model: "linear",
maxStartPrice: 0.5,
maxCpuPerHourPrice: 1.0,
maxEnvPerHourPrice: 0.5,
},
},
});
try {
const result = await executor.run(async (exe) => (await exe.run("node -v")).stdout);
console.log("Task result:", result);
} catch (err) {
console.error("An error occurred:", err);
} finally {
await executor.shutdown();
}
})();
Summary
We had created the simplest requestor script, that ran a single command on a remote computer. To achieve it we had:
- imported @golem-sdk/task-executor lib (and logger)
- utilized Immediately Invoked Async Function Expression
- created Task Executor
- defined a task as a function that runs our command
- finally read the command result from the result object and provide it to the user
In this example, we ran a simple command (node -v) in a shell on the remote computer. You can run other executable programs in more interesting scenarios. Go and explore our other examples.