Task

Summary

Tasks are a robust way to handle any mutating changes that need to interface with a remote API.

Tasks help you handle and encapsulate optimistic updates, rollbacks, undo/redo, API responses, API errors, queuing, and multi-step dependencies.

They are especially useful in offline mode. Users may have taken tons of actions that we've queued up to process when they come back online.

Tasks represent individual changes to the datastore that alter the local cache and need to be synced back to the server.

To create your own task, subclass Task and implement the following required methods:

See their usage in the documentation below.

Task Dependencies

The Task system handles dependencies between multiple queued tasks. For example, the {SendDraftTask} has a dependency on the {SyncbackDraftTask} (aka saving) succeeding. To establish dependencies between tasks, your subclass may implement one or more of the following methods:

Undo / Redo

The Task system also supports undo/redo handling. Your subclass must implement the following methods to enable this:

Offline Considerations

All tasks should gracefully handle the case when there is no network connection.

if we're offline the common behavior is for a task to:

  1. Perform its local change
  2. Attempt the remote request, which will fail
  3. Have performRemote resolve a Task.Status.Retry
  4. Sit queued up waiting to be retried

Remember that a user may be offline for hours and perform thousands of tasks in the meantime. It's important that your tasks implement shouldDequeueOtherTask and isDependentOnTask to make sure ordering always remains correct.

Serialization and Window Considerations

The whole {TaskQueue} and all of its Tasks are serialized and stored in the Database. This allows the {TaskQueue} to work across windows and ensures we don't lose any pending tasks if (a user is offline for a while) and quits and relaunches the application.

All instance variables you create must be able to be serialized to a JSON string and re-inflated. Notably, function objects will not be properly re-inflated.

if (you have instance variables that are instances of core {Model}) classes or {Task} classes, they will be automatically re-inflated to the correct class via Utils::deserializeRegisteredObject. if (you create) your own custom classes, they must be registered once per window via TaskRegistry::register

Example Task

Task Definition:

import _ from 'underscore'
import request from 'request'
import {Task, DatabaseStore} from 'nylas-exports'

class UpdateTodoTask extends Task {
constructor(existingTodo, newData) {
super()
this.existingTodo = existingTodo;
this.newData = newData;
}

performLocal() {
this.updatedTodo = _.extend(_.clone(this.existingTodo), this.newData);
return DatabaseStore.inTransaction((t) => t.persistModel(this.updatedTodo));
}

performRemote() {
return new Promise (resolve, reject) => {
const options = {url: "https://myapi.co", method: 'PUT', json: this.newData}
request(options, (error, response, body) => {
if (error) {
resolve(Task.Status.Failed);
}
resolve(Task.Status.Success);
});
};
};
}

Task Usage:

import {Actions} from 'nylas-exports';
import UpdateTodoTask from './update-todo-task';

someMethod() {
...
const task = new UpdateTodoTask(existingTodo, {name: "Test"});
Actions.queueTask(task);
...
}

This example UpdateTodoTask does not handle undo/redo, nor does it rollback the changes if (there's an API error. See examples in) Task::performLocal for ideas on how to handle this.

Instance Methods

constructor()

Override the constructor to pass initial args to your Task and initialize instance variables. **IMPORTANT:** if (you override the constructor, be sure to call) `super`. On construction, all Tasks instances are given a unique `id`.

performRemote(Task.Status.SuccessTask.Status.RetryTask.Status.ContinueTask.Status.Failed)

**Required** | Put the actual API request code here. You must return a {Promise} that resolves to one of the following status constants: The resolved status will determine what the {TaskQueue} does with this task when it is finished. This is where you should put your actual API code. You can use the node `request` library to easily hit APIs, or use the {NylasAPI} class to talk to the [Nylas Platform API](https://nylas.com/cloud/docs/). Here is a more detailed explanation of Task Statuses: ### Task.Status.Success Resolve to `Task.Status.Success` when the task successfully completes. Once done, the task will be dequeued and logged as a success. ### Task.Status.Retry if (you resolve `Task.Status.Retry`, the task will remain on the queue) and tried again later. Any other task dependent on the current one will also continue waiting. `Task.Status.Retry` is useful if (it looks like we're offline, or you) get an API error code that indicates temporary failure. ### Task.Status.Continue Resolving `Task.Status.Continue` will silently dequeue the task, allow dependent tasks through, but not mark it as successfully resolved. This is useful if (you get permanent API errors, but don't really care) if (the task failed.) ### Task.Status.Failed if (you catch a permanent API error code (like a 500), or something) else goes wrong then resolve to `Task.Status.Failed`. Resolving `Task.Status.Failed` will dequeue this task, and **dequeue all dependent tasks**. You can optionally return the error object itself for debugging purposes by resolving an array of the form: `[Task.Status.Failed, errorObject]` You should not `throw` exceptions. Catch all cases yourself and determine which `Task.Status` to resolve to. if (due to programmer) error an exception is thrown, our {TaskQueue} will catch it, log it, and deal with the task as if (it resolved `Task.Status.Failed`.)

Parameters

Argument Description
Task.Status.Success
Task.Status.Retry
Task.Status.Continue
Task.Status.Failed

Returns

Return Values
Returns a {Promise} that resolves to a valid `Task.Status` type.

isDependentOnTask(other)

determines which other tasks this one is dependent on. Any task that passes the truth test will be considered a "dependency". if a "dependency" has a `Task.Status.Failed`, then all downstream tasks will get dequeued recursively for any of the downstream tasks that return true for `shouldBeDequeuedOnDependencyFailure` A task will also never be run at the same time as one of its dependencies.

Parameters
Argument Description
other An instance of a {Task} you must test to see if (it's a) dependency of this one.

Returns

Return Values
Returns `true` (is dependent on) or `false` (is not dependent on)

shouldDequeueOtherTask(other)

determines which other tasks this one should dequeue when it is first queued. Any task that passes the truth test will be considered "obsolete" and dequeued immediately. This is particularly useful in offline mode. Users may queue up tons of tasks but when we come back online to process them, we only want to process the latest one.

Parameters
Argument Description
other An instance of a {Task} you must test to see if (it's now) obsolete.

Returns

Return Values
Returns `true` (should dequeue) or `false` (should not dequeue)

shouldBeDequeuedOnDependencyFailure()

determines if the current task should be dequeued if one of the tasks it depends on fails.

Returns

Return Values
Returns `true` (should dequeue) or `false` (should not dequeue)

isUndo()

It's up to you to determine how you want to indicate whether or not you have an instance of an "Undo Task". We commonly use a simple instance variable boolean flag.

Returns

Return Values
Returns `true` (is an Undo Task) or `false` (is not an Undo Task)

canBeUndone()

Determines whether or not this task can be undone via the {UndoRedoStore}

Returns

Return Values
Returns `true` (can be undone) or `false` (can't be undone)

createUndoTask()

Return from `createIdenticalTask` and set a flag so your `performLocal` and `performRemote` methods know that this is an undo task.

createIdenticalTask()

Return a deep-cloned task to be used for an undo task

cancel()

code to run if (someone tries to dequeue your task while it is) in flight.

label()

(optional) A string displayed to users when your task is run. When tasks are run, we automatically display a notification to users of the form "label (numberOfImpactedItems)". if (this does not a return) a string, no notification is displayed

numberOfImpactedItems()

A string displayed to users indicating how many items your task affected.

results matching ""

    No results matching ""