Last active
April 27, 2023 15:15
-
-
Save awoimbee/a3a2c9d72f0b88420a97397f246aa292 to your computer and use it in GitHub Desktop.
Deferred values with pulumi-kubernetes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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