@polkadot/api
Version:
Promise and RxJS wrappers around the Polkadot JS RPC
73 lines (61 loc) • 3.15 kB
JavaScript
// Copyright 2017-2022 @polkadot/api authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { catchError, EMPTY, tap } from 'rxjs';
import { assert, isFunction } from '@polkadot/util';
// a Promise completion tracker, wrapping an isComplete variable that ensures
// that the promise only resolves once
export function promiseTracker(resolve, reject) {
let isCompleted = false;
return {
reject: error => {
if (!isCompleted) {
isCompleted = true;
reject(error);
}
return EMPTY;
},
resolve: value => {
if (!isCompleted) {
isCompleted = true;
resolve(value);
}
}
};
} // extract the arguments and callback params from a value array possibly containing a callback
function extractArgs(args, needsCallback) {
const actualArgs = args.slice(); // If the last arg is a function, we pop it, put it into callback.
// actualArgs will then hold the actual arguments to be passed to `method`
const callback = args.length && isFunction(args[args.length - 1]) ? actualArgs.pop() : undefined; // When we need a subscription, ensure that a valid callback is actually passed
assert(!needsCallback || isFunction(callback), 'Expected a callback to be passed with subscriptions');
return [actualArgs, callback];
} // Decorate a call for a single-shot result - retrieve and then immediate unsubscribe
function decorateCall(method, args) {
return new Promise((resolve, reject) => {
// single result tracker - either reject with Error or resolve with Codec result
const tracker = promiseTracker(resolve, reject); // encoding errors reject immediately, any result unsubscribes and resolves
const subscription = method(...args).pipe(catchError(error => tracker.reject(error))).subscribe(result => {
tracker.resolve(result);
setTimeout(() => subscription.unsubscribe(), 0);
});
});
} // Decorate a subscription where we have a result callback specified
function decorateSubscribe(method, args, resultCb) {
return new Promise((resolve, reject) => {
// either reject with error or resolve with unsubscribe callback
const tracker = promiseTracker(resolve, reject); // errors reject immediately, the first result resolves with an unsubscribe promise, all results via callback
const subscription = method(...args).pipe(catchError(error => tracker.reject(error)), tap(() => tracker.resolve(() => subscription.unsubscribe()))).subscribe(result => {
// queue result (back of queue to clear current)
setTimeout(() => resultCb(result), 0);
});
});
}
/**
* @description Decorate method for ApiPromise, where the results are converted to the Promise equivalent
*/
export function toPromiseMethod(method, options) {
const needsCallback = !!(options && options.methodName && options.methodName.includes('subscribe'));
return function (...args) {
const [actualArgs, resultCb] = extractArgs(args, needsCallback);
return resultCb ? decorateSubscribe(method, actualArgs, resultCb) : decorateCall((options === null || options === void 0 ? void 0 : options.overrideNoSub) || method, actualArgs);
};
}