suspenders-js
Version:
Asynchronous programming library utilizing coroutines, functional reactive programming and structured concurrency.
129 lines (112 loc) • 3.23 kB
text/typescript
import { CancelFunction, Result, ResultCallback, Suspender } from "./Types";
/**
* Starts the suspender but doesn't wait for it's result. Returns a new suspender that returns the
* result.
* @param {Suspender<T>} suspender
* @return {Suspender<T>}
*/
export const async = <T>(suspender: Suspender<T>): Suspender<T> => {
let result: Result<T> | undefined;
let resultCallback: ResultCallback<T> | undefined;
return (resCallback) => {
if (result !== undefined) {
resCallback(result);
} else {
resultCallback = resCallback;
}
return suspender((res) => {
if (resultCallback !== undefined) {
resultCallback(res);
} else {
result = res;
}
});
};
}
/**
* Returns the first suspender to resolve successfully. All other pending suspenders are canceled.
* If all suspenders error, throws the last error.
* @params {Array<Suspender<T>>} suspenders
* @return {Suspender<T>}
*/
export const race = <T>(...suspenders: Array<Suspender<T>>): Suspender<T> => {
return (resultCallback) => {
const cancelCallbacks: Array<CancelFunction | void | null> = [];
for (let [index, suspender] of suspenders.entries()) {
cancelCallbacks.push(suspender((value) => {
cancelCallbacks[index] = null;
// cancel all other suspenders
for (let m = 0; m < suspenders.length; m++) {
const cancel = cancelCallbacks[m];
if (cancel) {
cancel();
} else {
cancelCallbacks[m] = null;
}
}
resultCallback(value);
}));
}
return () => {
for (const cancelCallback of cancelCallbacks) {
if (cancelCallback) {
cancelCallback();
}
}
};
};
}
/**
* Returns a suspender that resolves with given timout.
* @param {number} millis
* @return {Suspender<void>}
*/
export const wait = (millis: number): Suspender<void> => {
return (resultCallback) => {
const timeout = setTimeout(() => {
resultCallback({ value: undefined });
}, millis);
return () => {
clearTimeout(timeout);
};
};
}
export const awaitCancelation = (): Suspender<void> => {
return () => {};
}
/**
* Converts a Promise<T> to a Suspender<T>.
* @param {Promise<T>} promise
* @return {Suspender<T>}
*/
export const promiseSuspender = <T>(promise: Promise<T>): Suspender<T> => {
return (resultCallback: ResultCallback<T>) => {
promise.then(
(value) => { resultCallback({ value }); },
(error) => { resultCallback({ tag: `error`, error}); }
);
};
}
/**
* Performs an http get on url. Returns body on 200 or throws error.
* @param {string} url
* @return {Suspender<T>}
*/
export const httpGet = (url: string): Suspender<string> => {
return (resultCallback) => {
const xhttp = new XMLHttpRequest();
xhttp.onloadend = function() {
if (this.status === 200) {
resultCallback({ value: this.responseText });
} else {
resultCallback({
tag: `error`,
error: new Error(`code: ${this.status} text: ${this.statusText}`)
});
}
};
xhttp.open(`GET`, url, true);
xhttp.send();
return () => { xhttp.abort(); };
};
}