@davidcal/fec-raptorq
Version:
Node.js wrapper for RaptorQ forward error correction
137 lines (121 loc) • 5.08 kB
JavaScript
import { bind_callable } from "./bind_callable.js";
const unsuspended_promise_ = (promise_like, ctx) => {
// Note: In this code we manually use `String` as some objects, such as Symbols, do not convert implicitely to strings, even within template literals.
// The use of `toString` is avoided in favour of `String`, a less error-prone alternative.
const promise = Promise.resolve(promise_like);
return new Proxy((...args) => {
return unsuspended_promise_((async () => {
const api = await promise;
if (typeof api !== "function") {
if (ctx?.$$$_READING) {
throw new TypeError(`${api} is not a function (reading '${ctx.$$$_READING.toString()}')`);
}
throw new TypeError(`${api} is not a function`);
}
return await Reflect.apply(api, api, args);
})(), {
$$$_READING: ctx?.$$$_READING ? `${ctx.$$$_READING}()` : "()",
});
}, {
get: (target, prop) => {
if (["then", "catch", "finally"].includes(prop)) {
return promise[prop].bind(promise);
}
if (["valueOf", "toString", "toLocaleString"].includes(prop)) {
return () => "<uoe/unsuspended_promise>";
}
if (["constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__proto__", Symbol.toPrimitive].includes(prop)) {
return target[prop];
}
return unsuspended_promise_((async () => {
const api = await promise;
if (false
// Motivation: In mirroring JavaScript's property access,
// we claim that `null` and `undefined` are the only two
// cases where property access results in a TypeError
// being thrown.
|| api === null
|| api === undefined
) {
if (ctx?.$$$_READING) {
throw new TypeError(`Cannot read properties of ${String(api)} (reading '${String(ctx.$$$_READING)}.${String(prop)}')`);
}
throw new TypeError(`Cannot read properties of ${String(api)} (reading '${String(prop)}')`);
}
if (typeof api[prop] === "function") {
return bind_callable(api[prop], api);
}
return api[prop];
})(), {
$$$_READING: ctx?.$$$_READING ? `${String(ctx.$$$_READING)}.${String(prop)}` : prop,
});
},
});
};
/**
* @stability 3 - stable
*
* An almost drop-in replacement for promises. Takes in a promise-like or non-promise value and returns a glorified promise. Analogous to `Promise.resolve`.
*
* Unsuspended promises incorporate an unsuspension mechanism.
*
* Wraps an api or promise to an api such that the api is immediately available. Properties and methods, including sub-properties of the api and sub-properties of the return-values of methods, are proxied to be immediately accessible before the api has neccesserily been resolved, and before parent properties and return-values of parent methods have been resolved. However, this means that the properties and methods are forced to be async.
*
* Upon access of a property or invocation of the wrapped api, it will first wait for the underlying api to be resolved before proceeding.
*
* If errors are reported by the passed-in promise, these errors will instead be delivered as internal errors inside any promises obtained from the wrapped api.
*
* Warning: The `then`, `catch` and `finally` properties must not be defined on the underlying api as this makes the underlying api promise-like, while also conflicting with our implementation of the promise interface at every level.
* Doing this will cause bizarre behaviour which may be difficult to track down.
*
* @example
*
* const api = unsuspended_promise(api_promise);
*
* // The following two lines of code are equivalent
* await api;
* await api_promise;
*
* // The following two lines of code are equivalent
* await api();
* await (await api_promise)();
*
* // The following two lines of code are equivalent
* await api.property;
* await (await api_promise).property;
*
* // The following three lines of code are equivalent
* await api.method();
* await unsuspended_promise(await api.method)();
* await (await (await api_promise).method)();
*
* // The following four lines of code are equivalent
* await api.method().property;
* await unsuspended_promise(await api.method()).property;
* await unsuspended_promise(await api.method()).property;
* await unsuspended_promise(unsuspended_promise(await api.method)()).property;
* await (await (await (await api_promise).method)()).property;
*
* // As you can see, unsuspending an api removes a lot of await statements.
* // This offers a high degree of flexibility when working with asynchronous code.
*
* @example
*
* const create_opengl_api = async () => {
* const gl = await init_opengl();
*
* return {
* draw_triangle: async () => {
* await gl.draw_three_lines();
* },
* };
* };
*
* const opengl_api = unsuspended_promise(create_opengl_api());
* await opengl_api.draw_triangle();
* console.log("triangle drawn");
*/
export const unsuspended_promise = (api_promise_like) => {
return unsuspended_promise_(api_promise_like);
};
export const unsuspendedPromise = unsuspended_promise;