JS Task API Examples: working with results
Introduction
This example will show you how the task result can be managed by code in different ways for different use-cases.
Prerequisites
Yagna service is installed and running with the try_golem
app-key configured.
How to run examples
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
Some of the examples require a simple worker.mjs
script that can be created with the following command:
echo console.log("Hello Golem World!"); > worker.mjs
Copy the code into the index.mjs
file in the project folder and run:
node index.mjs
Single command task
Let's look at the simple example: we will run a task that consists of single command and will print the content of the result object:
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"));
console.log("Task result:", result);
} catch (err) {
console.error("Error during the task:", err);
} finally {
await executor.shutdown();
}
})();
In this example, our task consists of a single command: node -v
. ctx.run()
which returns an object that is then passed to the result
variable and printed.
Index refers to the sequential number of a command (we have just one, and counting starts from 0), result
field of the result object is "ok" which indicates the command was completed successfully, and the actual results of the command are under stdout
.
Multi-command task
When you run your tasks in a batch that is concluded with .end()
:
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) => {
const res = await exe
.beginBatch()
.uploadFile("./worker.mjs", "/golem/input/worker.mjs")
.run("node /golem/input/worker.mjs > /golem/input/output.txt")
.run("cat /golem/input/output.txt")
.downloadFile("/golem/input/output.txt", "./output.txt")
.end();
return res;
});
console.log(result);
} catch (error) {
console.error(error);
} finally {
if (executor) await executor.shutdown();
}
})();
you will receive an array of result objects:
In case you end your batch with the endStream()
method:
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) => {
const res = await exe
.beginBatch()
.uploadFile("./worker.mjs", "/golem/input/worker.mjs")
.run("node /golem/input/worker.mjs > /golem/input/output.txt")
.run("cat /golem/input/output.txt")
.downloadFile("/golem/input/output.txt", "./output.txt")
.endStream();
return new Promise((resolve, reject) => {
res.subscribe({
next: (result) => console.log(result),
error: (error) => reject(error),
complete: () => resolve(),
});
});
});
} catch (err) {
console.error("An error occurred:", err);
} finally {
await executor.shutdown();
}
})();
as a result you get an Observable rxjs object on which you can subscribe to appropriate events
What to do if your command fails?
When your command fails, the ExeUnit (the component responsible for running your image on the remote computer) will terminate all remote processes. As a result, the entire task will be terminated.
What will happen in such a case depends on the way your task is composed. Let's see it in examples.
In the below case, the user's commands are chained in a batch. An error occurs as the user tries to download the output.txt
file from /golem/output/
folder while the file was created in the golem/input
folder. This command will raise an error and the whole task will be terminated. The next command, listing the content of /golem/
folder, will not be executed at all.
import { TaskExecutor } from "@golem-sdk/task-executor";
import { pinoPrettyLogger } from "@golem-sdk/pino-logger";
(async () => {
const executor = await TaskExecutor.create({
logger: pinoPrettyLogger(),
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 results = await executor.run(async (exe) => {
const res = await exe
.beginBatch()
.run("cat /golem/input/output.txt > /golem/input/output.txt")
.downloadFile("/golem/output/output.txt", "./output.txt") // there is no such file in output folder
.run("ls -l /golem/")
.end();
return res;
});
// TE will not be terminated on command error, user should review the results and take action
for (const commandResult of results) {
if (commandResult.result != "Ok") {
console.log("\n", "\x1b[31m", commandResult.message, "\n", "\x1b[0m");
break;
}
}
} catch (error) {
console.error("An error occurred:", error);
} finally {
await executor.shutdown();
}
})();
While the user will receive the error message, the output is only for the failing command, not for all commands in the task.
The level of detail in the message depends on the type of method that causes the error.
In the case of the data transfer method, you will receive a message describing the cause of the error.
Let's see another example:
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 {
// there is a mistake and instead of 'node -v' we call 'node -w'
const result = await executor.run(async (exe) => await exe.run("node -w"));
console.log("Task result:", result);
} catch (err) {
console.error("Error during the task:", err);
} finally {
await executor.shutdown();
}
})();
In the case of the failure in the run()
method, we receive the result object with following attributes:
result: 'Error'
,stdout: null
,stderr: 'node: bad option: -w\n',
- the command outputmessage: 'ExeScript command exited with code 9'
- message from the system. Thenode.js
exit code 9 means:Exit Code 9, Invalid Argument: This is employed when an unspecified option was given
.