Skip to content

Instantly share code, notes, and snippets.

@awoimbee
Last active April 27, 2023 15:15
Show Gist options
  • Save awoimbee/a3a2c9d72f0b88420a97397f246aa292 to your computer and use it in GitHub Desktop.
Save awoimbee/a3a2c9d72f0b88420a97397f246aa292 to your computer and use it in GitHub Desktop.
Deferred values with pulumi-kubernetes
import * as pulumi from "@pulumi/pulumi";
import assert from "node:assert/strict";
// Short solution:
type DeferredOutput<T> = pulumi.Output<T> & {
resolve: (value: pulumi.Input<T>) => void,
reject: (reason?: any) => void;
}
export function deferredOutput<T>() {
let _resolve;
let _reject;
let prom = new Promise<pulumi.Input<T>>((resolve, reject) => {
_resolve = resolve;
_reject = reject;
});
assert(_resolve !== undefined);
assert(_reject !== undefined);
let output = pulumi.output(prom) as DeferredOutput<pulumi.Unwrap<T>>;
output.resolve = _resolve;
output.reject = _reject;
return output;
}
// Full deferred promise:
/**
* Deferred promise
* Allows passing around Promises that do not have an executor.
* This is FREAKING DANGEROUS
* because it creates promises that, by default, never resolve.
*/
export class Deferred<T> extends Promise<T> {
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
constructor () {
let _resolve;
let _reject;
super((resolve, reject) => {
_resolve = resolve;
_reject = reject;
});
assert(_resolve !== undefined);
assert(_reject !== undefined);
this.resolve = _resolve;
this.reject = _reject;
}
static get [Symbol.species] () {
return Promise;
}
get [Symbol.toStringTag] () {
return "DeferredPromise";
}
}
type MagicDeferred<T> = T extends pulumi.Input<infer U>
? U extends object ? _MagicDeferredObject<T> : T // eslint-disable-line no-use-before-define
: T extends object ? _MagicDeferredObject<T> : Promise<T> // eslint-disable-line no-use-before-define
type _MagicDeferredObject<T> = {
[P in keyof T] : MagicDeferred<T[P]>
}
/**
* Magic wrapper to quickly make deferred versions of objects.
*/
export function deferred<T> (promise?: Promise<T>): Deferred<T> & MagicDeferred<T> {
if (promise === undefined) {
promise = new Deferred<T>();
}
// Use Proxy to catch property accesses and defer them
const obj = new Proxy(promise, {
get: (target, property) => {
// Don't defer access to `Promise`'s properties (eg `then`)
if (property in target) {
let value = (target as any)[property];
if (typeof value === "function") {
value = value.bind(target);
}
return value;
}
// return deferred property access
return deferred(target.then((value: any) => value[property]));
}
});
return obj as any;
}
// ---
function test(toto: pulumi.Input<string>) {
pulumi.output(toto).apply(resolved => console.log(resolved));
}
const ghost = deferred<{a: {b: {z: string}}}>();
test(ghost.a.b.z);
ghost.resolve({a: {b: {z: "Hello World !"}}})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment