Skip to content

Instantly share code, notes, and snippets.

@dmorosinotto
Created October 14, 2024 01:14
Show Gist options
  • Save dmorosinotto/a88c96b7dfe1c570dd8964db24c46f1b to your computer and use it in GitHub Desktop.
Save dmorosinotto/a88c96b7dfe1c570dd8964db24c46f1b to your computer and use it in GitHub Desktop.
PromiseEX - Extends Promise with $status Signal + cancel: AbortSignal controller
import { signal } from "@angular/core";
import type { Signal, WritableSignal } from "@angular/core";
export type PromiseStatus<T> = PromiseSettledResult<T> | { status: "pending" };
export interface PromiseEx<T> extends Promise<T>, Disposable, AsyncDisposable {
$status: Signal<PromiseStatus<T>>;
cancel: AbortSignal;
abort: AbortController["abort"];
}
export class PromiseEx<T> extends Promise<T> implements PromiseEx<T> {
constructor(p: Promise<T>) {
let abort: (reason?: any) => void;
super((resolve, reject) => {
abort = reject;
resolve(p);
});
this.$status = signal({ status: "pending" });
const ctrl = new AbortController();
this.cancel = ctrl.signal;
this.abort = ctrl.abort;
this.cancel.addEventListener("abort", () => {
console.error("-X- ADESSO ABORT", this.cancel.reason);
abort(this.cancel.reason); // reject the promise
});
console.log("|-- ADESSO INIT $status", JSON.stringify(this.$status()));
Promise.allSettled([p]).then(([status]) => {
console.log("--> ADESSO SET $status", JSON.stringify(status));
(this.$status as WritableSignal<PromiseStatus<T>>).set(status);
});
}
[Symbol.dispose](reason?: any) {
this.abort(reason); // ALLOW using SINTAX
}
async [Symbol.asyncDispose]() {
this.abort(); // ALLOW await using SINTAX
}
}
import { Component, computed, effect, signal, inject } from "@angular/core";
import type { Signal, WritableSignal } from "@angular/core";
import { AsyncPipe, JsonPipe } from "@angular/common";
import { FakeApiService } from "../fake-api.service";
import { interval, take } from "rxjs";
import { toSignal } from "@angular/core/rxjs-interop";
@Component({
selector: "sample",
standalone: true,
imports: [AsyncPipe, JsonPipe],
template: `
@let res = $data() | async;
<h3>Data: {{ res }}</h3>
<pre>{{ $status() | json }}</pre>
@if (!$isPending()) {
<button (click)="change()">Next</button>
}
`,
})
export default class HomeComponent {
api = inject(FakeApiService);
$id = signal(1);
$data = computed(() => new PromiseEx(this.api.getById(this.$id()))); // $id() is a signal
$timer = toSignal(interval(1000).pipe(take(15)), { initialValue: 0 });
$status = computed(() => this.$data().$status());
$isPending = computed(() => this.$data().$status().status === "pending");
async init() {
console.clear();
console.log("START TEST SIGNAL");
after().then(this.change);
console.log("PRE isPending:", this.$isPending());
console.log("PRE status:", this.$data().$status().status);
console.log("HERE:", await this.$data());
after(2).then((_) => {
console.log("POST status:", this.$data().$status().status);
console.log("POST isPensing:", this.$isPending());
});
after(10).then(this.change);
}
change = () => {
console.error("change ++$id");
this.$id.update((n) => n + 1);
};
constructor() {
this.init();
effect(() => {
console.log("EFFECT id:", this.$id());
console.error("id change -> START REALOAING DATA!");
});
effect(async () => {
console.log("ASYNC EFFET");
const res = await this.$data();
const status = this.$data().$status();
const pending = this.$isPending();
console.warn("EFFECT res:", res, pending, status);
});
effect(async () => {
const status = this.$data().$status();
const pending = this.$isPending();
console.warn("$$$", pending, JSON.stringify(status));
});
effect(() => {
console.info(`...${this.$timer()}s`);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment