Azure Core LRO client library for JavaScript
Azure's Core LRO is a JavaScript library that provides an API that aims to
allow the azure-sdk-for-js public libraries to implement fully featured pollers
to manage Long Running Operations (LROs) with the Azure services.
@azure/core-lro is made following our guidelines here: https://azure.github.io/azure-sdk/typescript_design.html#ts-lro
Source code
Getting started
Install the package
To install this package locally, use npm install --save @azure/core-lro
.
Configure TypeScript
TypeScript users need to have Node type definitions installed:
npm install @types/node
They will also need to enable compilerOptions.allowSyntheticDefaultImports
in their
tsconfig.json
. Note that if you have enabled compilerOptions.esModuleInterop
,
allowSyntheticDefaultImports
is enabled by default.
See TypeScript's compiler options handbook
for more information.
Key concepts
@azure/core-lro makes a distinction between the Long Running Operation and its Poller.
- Whenever we talk about an operation, we mean the static representation of a Long Running Operation.
Any operation will have a definition of a state, which has an opinionated default set of properties.
The definition of the operation will also have functions that will define how to request new information
about the pending operation, how to request its cancellation, and how to serialize it.
- A Poller is an object who's main function is to interact with an operation until the poller is manually stopped,
the operation finishes (either by succeeding or failing) or if a manual request to cancel the operation has succeeded.
Some characteristics of the pollers are:
- Pollers show the status of the polling behavior.
- Pollers support manual as well as automatic polling.
- Pollers are serializable and can resume from a serialized operation.
- Pollers also specify how much of the operation's state is going to be available to the public.
- A PollerLike is the public interface of a Poller, which allows for different implementations to be used.
Examples
You will be able to find some working examples of an implementation of an operation and a poller both in
our test files: https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/core/core-lro/test
as well as in the azure-sdk-for-js libraries that might be currently using core-lro, which can be found here:
https://github.com/Azure/azure-sdk-for-js/search?q="from+%40azure%5C%2Fcore-lro"&unscoped_q="from+%40azure%5C%2Fcore-lro"
Implementing an operation
To work with a Long Running Operation, we need to define how to update, cancel
and serialize the information related to the remote pending task.
To implement this class for your library or application,
you'll need to import the definitions of a poll operation and its state.
import { PollOperationState, PollOperation } from "@azure/core-lro";
A PollOperation
is an interface that defines how to update the local reference of the state
of the remote long running operation, just as well as how to request the cancellation of the same operation.
Besides updating and cancelling, it also defines how to serialize its information,
so that polling can be resumed at any time from a previously serialized operation
(more at Serializing an operation).
PollOperationState
provides a basic state for the poll operation.
It contains the minimal set of properties needed
to keep track of a long running operation, and it's intended to be extended
with any custom property that your program might need.
The state can be updated any time, but it should be updated at least in three opportunities:
when the operation starts, when it's finished, and when it's cancelled.
To be able to create your custom operations, you'll need to extend the
PollOperation
class with both your operation's state and the final result
value. For this example, we'll think on the final result value to be MyResult
,
which can be any type.
import { PollOperationState, PollOperation } from "@azure/core-lro";
export interface MyOperationState extends PollOperationState<MyResult> {
serviceClient: any;
myCustomString: string;
startedAt: Date;
finishedAt: Date;
}
export interface MyPollOperation extends PollOperation<MyOperationState, MyResult> {}
We recommend making a utility function to create new instances of your operation. Just like the following:
function makeOperation(
state: MyOperationState,
): MyOperation {
return {
state: {
...state,
},
update,
cancel,
toString
};
}
To properly define an operation, you will need to have implemented these three functions: update
, cancel
and toString
.
A guide on how to write them follows.
Your operation's update method
The update
method defines how to request the remote service for updates on the status of the long running operation.
It optionally receives an object with an abortSignal
property, from @azure/abort-controller's AbortSignalLike
.
A more advanced use allows passing in a fireProgress
function, which, if called, is responsible for triggering the
poller's onProgress
callbacks.
Here is an example of how to write an update method for your operation:
async function update(
this: MyOperation,
options: {
abortSignal?: AbortSignalLike;
fireProgress?: (state: MyOperationState) => void;
} = {}
): Promise<MyOperation> {
let isDone: boolean = false;
let doFireProgress: boolean = false;
if (isDone) {
this.state.completed = true;
this.state.result = "Done";
}
if (doFireProgress) {
options.fireProgress(state);
}
return makeOperation(this.state);
}
Your operation's cancel method
The operation's cancel
method should attempt to cancel the pending operation, if it's allowed by the remote service.
Otherwise, it should throw.
It only optionally receives an object with an abortSignal
property, from @azure/abort-controller's AbortSignalLike
.
It returns a promise that should be resolved with an updated version of the poller's operation.
async function cancel(
this: MyOperation,
options: { abortSignal?: AbortSignalLike } = {}
): Promise<MyOperation> {
return makeOperation(
{
...this.state,
cancelled: true
}
);
}
Serializing an operation
The operation's method toString
returns a string with a serialized representation of the operation.
It's useful when a poller wants to be resumed from a previously serialized state.
The deserialization of the operation has to be implemented within the constructor of a class that extends
the Poller class. You can read more at Resuming from a previous poller.
function toString(this: MyOperation): string {
return JSON.stringify({
state: {
...this.state,
}
});
}
Implementing a poller
@azure/core-lro's Poller
is a class that represents the definition of a program that polls
through consecutive requests until it reaches a state of completion.
A poller can be executed manually, by polling request by request by calling to the poll()
method repeatedly,
until its operation is completed.
It also provides a way to wait until the operation finishes, by calling pollUntilDone()
, which returns a promise.
Pollers can also request the cancellation of the ongoing process (internally using PollOperation
's cancel()
method).
The Poller needs two types to be defined, a type representing the state of the poller, which
must include a basic set of properties from PollOperationState<TResult>
(as mentioned in Implementing an operation), and a return type defined by TResult
, which can be anything.
To implement a poller, you must pull in the definitions of your operation and extend @azure/core-lro's Poller
class, as follows:
import { Poller } from "@azure/core-lro";
import { makeOperation, MyOperation, MyOperationState } from "./myOperation";
export class MyPoller extends Poller<MyOperationState, MyResult> {
constructor(
baseOperation?: MyOperation,
onProgress?: (state: MyOperationState) => void
) {
let state: MyOperationState = {};
if (baseOperation) {
state = baseOperation.state;
}
const operation = makeOperation(state);
super(operation);
if (onProgress) {
this.onProgress(onProgress);
}
}
async delay(): Promise<void> {
}
}
Once defined, you'll be able to use your poller as shown below:
const poller = new MyPoller();
await poller.poll();
const result = await poller.pollUntilDone();
The Poller class implements the PollerLike
interface, which allows poller implementations that avoid having
to export the Poller's class directly, and instead only export the already instantiated poller with the PollerLike
type.
An example of the definition of a client that returns an instantiated poller can be seen below:
class Client {
public async makePoller: PollerLike<MyOperationState, MyResult> {
const poller = new MyPoller({});
await poller.poll();
return poller;
}
}
const poller: PollerLike<MyOperationState, MyResult> = myClient.makePoller();
A poller can be created through its constructor, then it can be polled until it's completed.
At any point in time, the state of the poller can be obtained without delay through the getOperationState method.
At any point in time, the intermediate forms of the result type can be requested without delay.
Once the underlying operation is marked as completed, the poller will stop and the final value will be returned.
Here's an example usage of your client's poller:
const poller = myClient.makePoller();
const state: MyOperationState = poller.getOperationState();
const result: MyResult | undefined = poller.getResult();
const result: MyResult = await poller.pollUntilDone();
Resuming from a previous poller
Pollers by default have a method called toString
that invokes the operation's toString
method.
It can be used to store the state of a poller indefinitely, to then resume by creating another poller at any time in the future.
If an operation's toString
method is defined as follows:
function toString(this: TestOperation): string {
return JSON.stringify({
state: this.state
});
}
A custom implementation of a poller can deserialize it by receiving this string and converting it back to JSON, like in the following example:
export class MyPoller extends Poller<MyOperationState, string> {
constructor(
baseOperation: string | undefined
) {
let state: MyOperationState = {};
if (baseOperation) {
state = {
...JSON.parse(baseOperation).state,
...state
};
}
const operation = {
state,
}
super(operation);
}
}
Troubleshooting
Enable logs
Logs can be added at the discretion of the library implementing the Long Running Operation poller.
Packages inside of azure-sdk-for-js use
@azure/logger.
Next steps
Please take a look at the samples directory for detailed examples on how to use this library.
Contributing
This project welcomes contributions and suggestions. Please read the
contributing guidelines
for detailed information about how to contribute and what to expect while contributing.
Testing
To run our tests, first install the dependencies (with npm install
or rush install
),
then run the unit tests with: npm run unit-test
.
Code of Conduct
This project has adopted the Microsoft Open Source Code of Conduct.
For more information see the Code of Conduct FAQ or
contact opencode@microsoft.com with any additional questions or comments.