Abortable Promises for everyone !
@lirx/async-task
📦 Installation
yarn add @lirx/async-task
# or
npm install @lirx/async-task --save
🤕 The problem
Promises are great but they lack of cancellation
.
Usually, aborting a promise is done though an AbortSignal, but only a few APIs support it:
const controller = new AbortController();
fetch('https://example.com', {
signal: controller.signal,
})
.then((response: Response): Promise<any> => {
return response.json(); // sadly, it's not possible to abort this operation
})
.then((data: any): void => {
console.log(data);
});
setTimeout(() => {
controller.abort(new Error('Timeout'));
}, 1000);
When chaining Promises, the lack of a simple cancellation becomes frustrating, or conducts to errors or unwanted behaviours.
In the previous example, if the signal is aborted during the fetch, then the promise is cancelled as expected (rejected with an error), however, if this happens during the conversion to JSON, then it is resolved as usual which is an unwanted behaviour.
To solve this problem, we propose a new PromiseLike class called AsyncTask
, which natively support cancellation.
🔧 Example
const abortable = Abortable.timeout(1000);
const asyncTask = doAsyncTaskA(abortable)
.successful((data: string, abortable: Abortable):AsyncTask<string> => {
return doAsyncTaskB(data, abortable);
})
.errored((error: unknown, abortable: Abortable): AsyncTask<string> => {
return doAsyncTaskC(error, abortable);
})
.successful((data: string, abortable: Abortable): void => {
console.log(data);
});
It's possible to await
an AsyncTask
(await asyncTask;
), or to convert it into a regular Promise (await asyncTask.toPromise();
)
An AsyncTask is compatible with a Promise.
📑 Documentation
Abortable
This represents a "token" able to cancel an AsyncTask
.
It replaces the classes AbortControler
and AbortSignal
as one entity.
table of content:
- constructor
- properties:
- methods:
- static methods:
constructor
class Abortable {
constructor(init: (abort: IAbortFunction) => void);
}
type IAbortFunction = (reason: any) => void;
parameters
-
init
: a function to be executed by the constructor. It receives one function as parameter (abort
). Whenabort
is called, the Abortable is aborted.
return value
When called via new
, the Abortable
constructor returns an abortable object.
The abortable object will become aborted with a specific reason when the functions abort
is invoked.
example
Creates an Abortable
aborted after 1000ms:
const abortable = new Abortable((abort: IAbortFunction): void => {
setTimeout(() => abort(new Error('Timeou'), 1000));
});
get aborted
get aborted(): boolean;
Returns true if the Abortable
is aborted.
get reason
get reason(): any;
Returns the abort reason of the Abortable
.
If the Abortable
is not aborted, it returns undefined
.
onAbort
This method is used to subscribe to the abort event.
onAbort(onAbort: IAbortFunction): IAbortableUnsubscribe;
type type IAbortableUnsubscribe = () => void;
parameters
-
onAbort
: a function to be executed when the Abortable is aborted, or immediately if the Abortable is already aborted. It receives the abortreason
.
return value
A function to call when we want to unsubscribe of this event.
example
Subscribes to the abort
event:
const unsubscribe = abortable.onAbort((reason: any): void => {
console.log('aborted', reason);
});
toAbortSignal
This method is used to convert an Abortable
to an AbortSignal
.
toAbortSignal(): AbortSignal
This is useful when dealing with APIs supporting AbortSignal
.
return value
An AbortSignal
aborted when the Abortable
is aborted.
example
fetch(url, {
signal: abortable.toAbortSignal(),
})
static get never
static get never(): Abortable;
Returns an Abortable
, which is never aborted.
Useful in some situations where you never want to cancel an AsyncTask
.
static fromAbortSignal
Creates an Abortable
from an AbortSignal
.
static fromAbortSignal(signal: AbortSignal): Abortable
parameters
-
signal
: theAbortSignal
to create theAbortable
from.
return value
An Abortable
aborted when the AbortSignal
is aborted.
static abort
Creates an aborted Abortable
.
static abort(reason: any): Abortable
parameters
-
reason
: the reason why the operation was aborted.
return value
An Abortable
aborted with reason
.
static timeout
Creates an Abortable
aborted after a specified time.
static timeout(ms: number): Abortable
parameters
-
ms
: the time in milliseconds before the returnedAbortable
will abort.
return value
An Abortable
aborted after ms
milliseconds.
example
A simple example showing a fetch operation that will timeout if unsuccessful after 5 seconds:
fetch(url, {
signal: Abortable.timeout(5000).toAbortSignal(),
})
static derive
static derive(...abortables: Abortable[]): IDeriveAbortableResult;
type IDeriveAbortableResult = [
abort: IAbortFunction,
aborbale: Abortable,
];
parameters
-
...abortables
: a list ofAbortable
to build the returnedAbortable
from. If any of theseAbortable
is aborted, the returnedAbortable
is aborted too.
return value
A tuple composed of :
-
abort
: which is a function having the same type and properties than the one received as parameter from theinit
function provided in the constructor. It may be used to abort the returnedAbortable
-
aborbale
: anAbortable
aborted if any of the providedAbortable
is aborted, or ifabort
is called.
example
A simple example showing a fetch operation that will be aborted immediately:
const [abort, abortable] = Abortable.derive();
const request = fetch(url, {
signal: abortable.toAbortSignal(),
});
abort();
static merge
static merge(...abortables: Abortable[]): Abortable;
parameters
-
...abortables
: a list ofAbortable
to build the returnedAbortable
from. If any of theseAbortable
is aborted, the returnedAbortable
is aborted too.
return value
An Abortable
aborted if any of the provided Abortable
is aborted.
example
A simple example showing a fetch operation that will be aborted after 1000ms:
const abortableA = Abortable.timeout(5000);
const abortableB = Abortable.timeout(1000);
const abortable = Abortable.merge(abortableA, abortableB);
const request = fetch(url, {
signal: abortable.toAbortSignal(),
});
AsyncTask
The AsyncTask
object represents the eventual completion, failure, or cancellation of an asynchronous operation and its resulting value.
It's an alternative to a Promise
, supporting cancellation.
It has a similar constructor, similar methods, and similar behaviour.
It simply completes the Promise
with a native support for cancellation
.
table of content:
- constructor
- methods:
- static methods:
constructor
class AsyncTask<GValue extends IAsyncTaskConstraint<GValue>> {
constructor(
init: IAsyncTaskInitFunction<GValue>,
abortable: Abortable,
);
}
type IAsyncTaskInitFunction<GValue extends IAsyncTaskConstraint<GValue>> = (
success: IAsyncTaskSuccessFunction<GValue>,
error: IAsyncTaskErrorFunction,
abortable: Abortable,
) => void;
type IAsyncTaskSuccessFunction<GValue extends IAsyncTaskConstraint<GValue>> = (value: IAsyncTaskInput<GValue>) => void;
type IAsyncTaskErrorFunction = (error: any) => void;
type IAsyncTaskInput<GValue extends IAsyncTaskConstraint<GValue>> =
| AsyncTask<GValue>
| Promise<GValue>
| GValue
;
INFO: IAsyncTaskConstraint
is just a type constrain. It ensures that GValue
cannot be a Promise
nor an AsyncTask
.
This fixes some Promise's issues with typing.
parameters
-
init
: a function to be executed by the constructor. It receives two functions as parameters:success
anderror
; and a third parameterabortable
. Any errors thrown in this function will cause theAsyncTask
to switch in an error state. -
abortable
: anAbortable
signaling theAsyncTask
to stop when aborted.
return value
When called via new
, the AsyncTask
constructor returns an asyncTask object.
The asyncTask object will become resolving when either of the functions success
or error
are invoked; or when the
provided Abortable
is aborted.
Note that if you call success
or error
and pass another AsyncTask
or Promise
object as an argument,
it can be said to be resolving, but still not resolved.
description
An Asynctask
is extremely similar to a Promise in its behaviour,
but it accepts an Abortable
as input to be able to cancel the operation.
Let's break down the parameters received by the init
function:
success
success: (value: IAsyncTaskInput<GValue>) => void
If the value
parameter passed to the success
function is:
- another
AsyncTask
orPromise
object: the newly constructedAsyncTask
's state will be "locked in" to the value passed. When this last one resolves, theAsyncTask
is resolved with the same state (a success or an error). - another value: the newly constructed
AsyncTask
switches to a success state with this value.
error
error: (error: any) => void
Similar to success
, the error
parameter passed to the error
function can be another AsyncTask
or Promise
object.
In this case, the AsyncTask
's state is locked in until this "value" is resolved, at which point, it switches to an error state with the provided error
.
If the provided error
is not an AsyncTask
or Promise
, then the AsyncTask
switches to an error state with this error.
abortable
abortable: Abortable
This is the Abortable
bound to this AsyncTask
.
If this Abortable
is aborted, then the AsyncTask
is automatically aborted.
This parameter is useful to clean an async operation using for example its onAbort
method.
example
Creates an AsyncTask
becoming successful after a specific period of time:
function asyncTimeout(
ms: number,
abortable: Abortable,
): AsyncTask<void> {
return new AsyncTask<void>((
success: IAsyncTaskSuccessFunction<void>,
error: IAsyncTaskErrorFunction,
abortable: Abortable,
): void => {
const timer = setTimeout(success, ms);
abortable.onAbort(() => {
clearTimeout(timer);
});
}, abortable);
}
then
The then()
method of an AsyncTask
object takes two arguments:
the callback functions for the success and error cases of the AsyncTask
.
It immediately returns an equivalent AsyncTask
object, allowing you to chain calls to other asyncTask methods.
then<GNewValue extends IAsyncTaskConstraint<GNewValue>>(
onSuccessful: IAsyncTaskOnSuccessfulFunction<GValue, GNewValue>,
onErrored: IAsyncTaskOnErroredFunction<GNewValue>,
): AsyncTask<GNewValue>
type IAsyncTaskOnSuccessfulFunction<GValue extends IAsyncTaskConstraint<GValue>, GNewValue extends IAsyncTaskConstraint<GNewValue>> = (
value: GValue,
abortable: Abortable,
) => IAsyncTaskInput<GNewValue>;
type IAsyncTaskOnErroredFunction<GNewValue extends IAsyncTaskConstraint<GNewValue>> = (
error: any,
abortable: Abortable,
) => IAsyncTaskInput<GNewValue>;
parameters
-
onSuccessful
: a function asynchronously called if theAsyncTask
is successful. This function has two parameters, the success value and theAbortable
linked to thisAsyncTask
. This function may return a value, anAsyncTask
or aPromise
. -
onRejected
: a function asynchronously called if theAsyncTask
is errored. This function has two parameters, the rejection reason and theAbortable
linked to thisAsyncTask
. This function may return a value, anAsyncTask
or aPromise
.
Unlike Promises, the two functions are mandatory. They can't be omitted nor null or undefined.
return value
Returns a new AsyncTask
immediately.
This new AsyncTask
is always pending when returned, regardless of the current AsyncTask
's status.
One of the onSuccessful
and onRejected
handlers will be executed to handle the current AsyncTask
's success or error.
The call always happens asynchronously, even when the current AsyncTask
is already resolved.
The behavior of the returned AsyncTask
(call it asyncTask
) depends on the handler's execution result, following a specific set of rules.
If the handler function:
- returns a value:
asyncTask
switches to a success state with the returned value as its value. - doesn't return anything:
asyncTask
switches to a success state withundefined
as its value. - throws an error:
asyncTask
switches to an error state with the thrown error as its value. - returns an already successful
AsyncTask
:asyncTask
switches to a success state with thatAsyncTask
's value as its value. - returns an already errored
AsyncTask
:asyncTask
switches to an error state with thatAsyncTask
's value as its value. - returns another pending
AsyncTask
:asyncTask
is pending and becomes switches to a success/error state with thatAsyncTask
's value as its value immediately after thatAsyncTask
becomes success/error.
If an AsyncTask
is returned, it MUST have the same abortable as the one provided as second argument (abortable
).
description
This function is compatible with the Thenable object.
In consequence, it's possible to await
an AsyncTask
.
example
const abortable = Abortable.never;
new AsyncTask<number>((success) => {
success(Math.random() * 1000);
}, abortable)
.then(
(value: number, abortable: Abortable): AsynTask<void> => {
return asyncTimeout(value, abortable);
},
(error: unknown, abortable: Abortable): never => {
console.error(error);
throw error;
},
);
successful
The successful()
method of an AsyncTask
is equivalent to the then()
method, but only with the onSuccessful
callback function.
successful<GNewValue extends IAsyncTaskConstraint<GNewValue>>(
onSuccessful: IAsyncTaskOnSuccessfulFunction<GValue, GNewValue>,
): AsyncTask<GNewValue>
If the current AsyncTask
switches to an error state, then the returned AsyncTask
switches to an error state too,
else the behaviour is the same as the one using the then()
method.
example
const abortable = Abortable.never;
new AsyncTask<number>((success) => {
success(Math.random() * 1000);
}, abortable)
.successful((value: number, abortable: Abortable): AsynTask<void> => {
return asyncTimeout(value, abortable);
})
.successful((): void => {
console.log('done !');
});
errored
The errored()
method of an AsyncTask
is equivalent to the then()
method, but only with the onErrored
callback function.
errored<GNewValue extends IAsyncTaskConstraint<GNewValue>>(
onErrored: IAsyncTaskOnErroredFunction<GNewValue>,
): AsyncTask<GValue | GNewValue>
If the current AsyncTask
switches to a success state, then the returned AsyncTask
switches to a success state too,
else the behaviour is the same as the one using the then()
method.
example
const abortable = Abortable.never;
new AsyncTask<number>((success) => {
error(new Error('Error !'));
}, abortable)
.errored((error: unknown, abortable: Abortable): AsynTask<void> => {
console.log('error catched', error);
return asyncTimeout(500, abortable);
})
.successful((): void => {
console.log('done !');
});
aborted
The aborted()
method is tricky and must be used with caution:
aborted<GNewValue extends IAsyncTaskConstraint<GNewValue>>(
onAborted: IAsyncTaskOnAbortedFunction<GNewValue>,
abortable?: Abortable,
): AsyncTask<GValue | GNewValue>
type IAsyncTaskOnAbortedFunction<GNewValue extends IAsyncTaskConstraint<GNewValue>> = (
reason: any,
abortable: Abortable,
) => IAsyncTaskInput<GNewValue>;
parameters
-
onAborted
: a function asynchronously called if theAsyncTask
is aborted. This function has two parameters, the abort reason value and anAbortable
to use if anAsyncTask
is returned. This function may return a value, anAsyncTask
or aPromise
. -
abortable
: this optional parameters, allows us to "switch" ofAbortable
. Indeed, the currentAsyncTask
is in an aborted state, so this parameter gives us the opportunity to create a newAsyncTask
with a differentAbortable
. If omitted, the currentAsyncTask
'sAbortable
is used instead.
return value
Returns a new AsyncTask
immediately, with abortable
as its own Abortable
.
This new AsyncTask
is always pending when returned, regardless of the current AsyncTask
's status.
The onAborted
handler will be executed to handle the current AsyncTask
's abort state.
The call always happens asynchronously, even when the current AsyncTask
is already resolved.
The behavior of the returned AsyncTask
is similar to the then()
method.
example
const abortable = Abortable.never;
new AsyncTask<number>((success) => {
success(Math.random() * 1000);
}, abortable)
.successful((ms: number, abortable: Abortable): never => {
// let's create an abortable which "races" between the received abortable ('abortable') and a timeout of 500ms
const sharedAbortable = Abortable.merge([
abortable,
Abortable.timeout(500),
]);
// in 50% of the time, it will abort before the following AsyncTask resolves
// we cannot return an AsyncTask with a different Abortable, because only one controller must exists
return asyncTimeout(ms, sharedAbortable)
// so we use the 'aborted()' method, with the original Abortable
.aborted((reason: unknown): never => {
// in which we throw an error
throw new Error(`Oops child AsyncTask aborted with: ${reason}`);
}, abortable);
});
switchAbortable
switchAbortable(abortable: Abortable): AsyncTask<GValue>
This is equivalent to:
return this.aborted<GValue>((reason: any): never => {
throw reason;
}, abortable);
example
const abortable = Abortable.never;
new AsyncTask<number>((success) => {
success(Math.random() * 1000);
}, abortable)
.successful((ms: number, abortable: Abortable): never => {
const sharedAbortable = Abortable.merge([
abortable,
Abortable.timeout(500),
]);
return asyncTimeout(ms, sharedAbortable)
.switchAbortable(abortable);
});
finally
finally(
onFinally: IAsyncTaskOnFinallyFunction<GValue>,
): AsyncTask<GValue>
type IAsyncTaskOnFinallyFunction<GValue extends IAsyncTaskConstraint<GValue>> = (
state: IAsyncTaskState<GValue>,
abortable: Abortable,
) => IAsyncTaskInput<void>
interface IAsyncTaskSuccessState<GValue extends IAsyncTaskConstraint<GValue>> {
readonly state: 'success';
readonly value: GValue;
}
interface IAsyncTaskFinalErrorState {
readonly state: 'error';
readonly error: any;
}
interface IAsyncTaskFinalAbortState {
readonly state: 'abort';
readonly reason: any;
}
type IAsyncTaskState<GValue extends IAsyncTaskConstraint<GValue>> =
| IAsyncTaskSuccessState<GValue>
| IAsyncTaskFinalErrorState
| IAsyncTaskFinalAbortState
;
parameters
-
onFinally
: a function asynchronously called if theAsyncTask
is resolved (successful/errored/aborted). This function has two parameters, the state of theAsyncTask
and anAbortable
to use if anAsyncTask
is returned. This function may return a value, anAsyncTask
or aPromise
.
return value
Returns a new AsyncTask
immediately.
The onFinally
handler will be executed when the current AsyncTask
is resolved.
The call always happens asynchronously, even when the current AsyncTask
is already resolved.
If an error is thrown in the onFinally
, a rejected Promised is returned or an errored AsyncTask
is returned,
then the newly created AsyncTask
will error too with this error.
Else, the newly created AsyncTask
will success, error or abort according to the current AsyncTask
state.
example
const readAll = (
reader: ReadableStreamDefaultReader<string>,
abortable: Abortable,
): AsyncTask<string> => {
return AsyncTask.fromFactory(() => reader.read(), abortable)
.successful((result: ReadableStreamReadResult<string>, abortable: Abortable) => {
if (result.done) {
return '';
} else {
return readAll(reader, abortable)
.successful((output: string): string => {
return result.value + output;
});
}
});
};
const decoder = new TextDecoderStream();
const reader = encoder.readable.getReader();
const writer = encoder.writable.getWriter();
const abortable = Abortable.never;
readAll(reader, abortable)
.successful((output: string): void => {
console.log('decoder', output);
})
.finally((): void => {
reader.releaseLock();
});
writer.write(new TextEncoder().encode('Hello world !'));
writer.close();
toPromise
Creates a Promise from an AsyncTask
.
toPromise(): Promise<GValue>
return value
Returns a Promise.
If the AsyncTask
resolves with the state:
- success: fulfill the promise with the result value.
- error: reject the promise with the result error.
- abort: reject the promise with an "Abort" error.
example
const abortable = Abortable.never;
asyncTimeout(1000, abortable)
.toPromise()
.then(() => {
console.log('done !');
});
static fromFactory
static fromFactory<GValue extends IAsyncTaskConstraint<GValue>>(
factory: IAsyncTaskFactory<GValue>,
abortable: Abortable,
): AsyncTask<GValue>
type IAsyncTaskFactory<GValue extends IAsyncTaskConstraint<GValue>> = (abortable: Abortable) => IAsyncTaskInput<GValue>;
parameters
-
factory
: a function returning a value, anAsyncTask
or aPromise
. It receives anAbortable
. -
abortable
: theAbortable
linked to the returnedAsyncTask
.
return value
- if
abortable
is aborted, returns an abortedAsyncTask
- else, calls
factory
:- if an error is thrown from the factory, returns an error
AsyncTask
- if an
AsyncTask
is returned, returns thisAsyncTask
- else, returns an
AsyncTask
resolved with this the return of thefactory
.
- if an error is thrown from the factory, returns an error
examples
AsyncTask.fromFactory<number>(() => 45, Abortable.never); // resolved with 45
AsyncTask.fromFactory<void>(() => console.log('never happend'), Abortable.abort('a')); // the factory function is never called
AsyncTask.fromFactory<number>(() => Promise.resolve(45), Abortable.never); // resolved with 45
AsyncTask.fromFactory<number>(() => Promise.reject('error !'), Abortable.never); // rejected with 'error !'
AsyncTask.fromFactory<number>(() => AsyncTask.success(45), Abortable.never); // returns the AsyncTask generated by the factory
static success
static success<GValue extends IAsyncTaskConstraint<GValue>>(
value: IAsyncTaskInput<GValue>,
abortable: Abortable,
): AsyncTask<GValue>
parameters
-
value
: a value, anAsyncTask
or aPromise
-
abortable
: theAbortable
linked to the returnedAsyncTask
.
return value
Returns an AsyncTask
resolved with value
.
This is equivalent to:
return new AsyncTask<GValue>((success: IAsyncTaskSuccessFunction<GValue>): void => {
success(input);
}, abortable);
example
AsyncTask.success<number>(45, Abortable.never);
static error
static error<GValue extends IAsyncTaskConstraint<GValue> = unknown>(
error: any,
abortable: Abortable,
): AsyncTask<GValue>
parameters
-
error
: a value, anAsyncTask
or aPromise
-
abortable
: theAbortable
linked to the returnedAsyncTask
.
return value
Returns an AsyncTask
rejected with error
.
This is equivalent to:
return new AsyncTask<GValue>((
success: IAsyncTaskSuccessFunction<GValue>,
_error: IAsyncTaskErrorFunction,
): void => {
_error(error);
}, abortable)
example
AsyncTask.error(new Error('Errored !'), Abortable.never);
static never
static never<GValue extends IAsyncTaskConstraint<GValue> = unknown>(
abortable: Abortable,
): AsyncTask<GValue>
parameters
-
abortable
: theAbortable
linked to the returnedAsyncTask
.
return value
Returns an AsyncTask
which never resolves (may only be aborted).
This is equivalent to:
return new AsyncTask<GValue>(() => {}, abortable);
static void
static void(
abortable: Abortable,
): AsyncTask<void>
parameters
-
abortable
: theAbortable
linked to the returnedAsyncTask
.
return value
Returns an AsyncTask
resolved with undefined
.
This is equivalent to:
return this.success<void>(void 0, abortable);
example
AsyncTask.void(Abortable.never);
static all
static all<GFactories extends IGenericAsyncTaskFactoriesList>(
factories: GFactories,
abortable: Abortable,
): AsyncTask<IAsyncTaskAllValuesListReturn<GFactories>>
parameters
-
factories
: an iterable ofIAsyncTaskFactory
. -
abortable
: theAbortable
linked to the returnedAsyncTask
.
return value
Returns an AsyncTask
resolved with all the values returned by the factories.
definition
Calls all the factories with an Abortable
derived from the provided abortable
called factoriesAbortable
.
Awaits on all the results to be resolved, and stores their returning values in an array called values
.
If all the results are in a success state OR the provided iterable is empty,
then the returned AsyncTask
is resolved with values
.
If any of the result is rejected, then the returned AsyncTask
is rejected too.
Moreover, factoriesAbortable
is aborted, meaning that other factories MUST be aborted.
This is extremely similar to Promise.all
, but works with factories instead.
If one of the factories rejects, then the other factories are cancelled, optimizing resources.
example
const abortable = Abortable.never;
const asyncTask = AsyncTask.all([
(abortableA: Abortable) => asyncTimeout(1000, abortableA),
(abortableB: Abortable) => AsyncTask.error(new Error('Error !'), abortableB),
], abortable);
In this example, all the factories are called.
The second one returns an errored AsyncTask
, so asyncTask
switches to an error state,
and abortableA
is aborted, effectively cleaning the pending timeout.
static race
static race<GFactories extends IGenericAsyncTaskFactoriesList>(
factories: GFactories,
abortable: Abortable,
): AsyncTask<IAsyncTaskRaceValueReturn<GFactories>>
parameters
-
factories
: an iterable ofIAsyncTaskFactory
. -
abortable
: theAbortable
linked to the returnedAsyncTask
.
return value
Returns an AsyncTask
resolved with the first value or error returned by the factories.
definition
Calls all the factories with an Abortable
derived from the provided abortable
called factoriesAbortable
.
Awaits on the first result to be resolved:
- if the result is in a success state, then the returned
AsyncTask
is resolved with this value. - if the result is in an error state, then the returned
AsyncTask
is rejected with this error.
Moreover, factoriesAbortable
is aborted, meaning that other factories MUST be aborted.
If the provided iterable is empty, the AsyncTask
never resolves.
This is extremely similar to Promise.race
, but works with factories instead.
If one of the factories succeeds or rejects, then the other factories are cancelled, optimizing resources.
example
const abortable = Abortable.never;
const asyncTask = AsyncTask.race([
(abortableA: Abortable) => asyncTimeout(1000, abortableA),
(abortableB: Abortable) => asyncTimeout(2000, abortableA),
], abortable);
In this example, all the factories are called.
The return of the first one finishes first with undefined
, so asyncTask
switches to a success state with undefined
as value.
abortableB
is aborted, effectively cleaning the pending second timeout.
static allSettled
Built-in functions
table of content:
asyncTimeout
function asyncTimeout(
ms: number,
abortable: Abortable,
): AsyncTask<void>
parameters
-
ms
: the number of milliseconds to wait until the AsyncTask resolves. -
abortable
: theAbortable
linked to the returnedAsyncTask
.
return value
Returns an AsyncTask
resolved after a specific amount of time.
example
const abortable = Abortable.never;
const asyncTask = AsyncTask.asyncTimeout(1000, abortable);
asyncFetch
function asyncFetch(
input: RequestInfo | URL,
init: IAsyncFetchRequestInit,
abortable: Abortable,
): AsyncTask<Response>
definition
Similar to the fetch()
function, but works with AsyncTask
instead.
example
const abortable = Abortable.timeout(2000);
asyncFetch('https://example.com', void 0, abortable)
.successful((response: Response, abortable: Abortable): AsyncTask<string> => {
if (response.ok) {
return AsyncTask.fromFactory<GData>(() => response.json(), abortable);
} else {
throw new Error(`Failed to fetch '${response.url}': ${response.status}`);
}
})
.successful((result: string) => {
console.log(result);
});
FAQ
Why not using AbortController and AbortSignal ?
Because I wasn't totally satisfied of these classes.
I wanted an object able to do both but keeping the principle of controller/worker.
Moreover, I wanted more static methods, helping developers to rapidly construct such an object.
This is where the idea of Abortable
came from.
Just like a Promise, after created, only the "creator" of the Abortable
can abort it.
And the same if true for an AsyncTask
. It simply cannot cancel itself, only the controller can do it.
This prevents bad patterns and undefined behaviours, even if it wasn't clear from the start.
Why not using Promise with AbortSignal ?
Because chaining promises with a common AbortSignal
is a nightmare and is prone to errors.
Using AsyncTask
gives you a robust framework to work with cancellable async operations.