2025-04-15 16:39:47 +02:00

103 lines
2.9 KiB
JavaScript

const GET_IS_ASYNC = Symbol.for("quansync.getIsAsync");
class QuansyncError extends Error {
constructor(message = "Unexpected promise in sync context") {
super(message);
this.name = "QuansyncError";
}
}
function isThenable(value) {
return value && typeof value === "object" && typeof value.then === "function";
}
function isQuansyncGenerator(value) {
return value && typeof value === "object" && typeof value[Symbol.iterator] === "function" && "__quansync" in value;
}
function fromObject(options) {
const generator = function* (...args) {
const isAsync = yield GET_IS_ASYNC;
if (isAsync)
return yield options.async.apply(this, args);
return options.sync.apply(this, args);
};
function fn(...args) {
const iter = generator.apply(this, args);
iter.then = (...thenArgs) => options.async.apply(this, args).then(...thenArgs);
iter.__quansync = true;
return iter;
}
fn.sync = options.sync;
fn.async = options.async;
return fn;
}
function fromPromise(promise) {
return fromObject({
async: () => Promise.resolve(promise),
sync: () => {
if (isThenable(promise))
throw new QuansyncError();
return promise;
}
});
}
function unwrapYield(value, isAsync) {
if (value === GET_IS_ASYNC)
return isAsync;
if (isQuansyncGenerator(value))
return isAsync ? iterateAsync(value) : iterateSync(value);
if (!isAsync && isThenable(value))
throw new QuansyncError();
return value;
}
const DEFAULT_ON_YIELD = (value) => value;
function iterateSync(generator, onYield = DEFAULT_ON_YIELD) {
let current = generator.next();
while (!current.done) {
try {
current = generator.next(unwrapYield(onYield(current.value, false)));
} catch (err) {
current = generator.throw(err);
}
}
return unwrapYield(current.value);
}
async function iterateAsync(generator, onYield = DEFAULT_ON_YIELD) {
let current = generator.next();
while (!current.done) {
try {
current = generator.next(await unwrapYield(onYield(current.value, true), true));
} catch (err) {
current = generator.throw(err);
}
}
return current.value;
}
function fromGeneratorFn(generatorFn, options) {
return fromObject({
name: generatorFn.name,
async(...args) {
return iterateAsync(generatorFn.apply(this, args), options?.onYield);
},
sync(...args) {
return iterateSync(generatorFn.apply(this, args), options?.onYield);
}
});
}
function quansync(input, options) {
if (isThenable(input))
return fromPromise(input);
if (typeof input === "function")
return fromGeneratorFn(input, options);
else
return fromObject(input);
}
function toGenerator(promise) {
if (isQuansyncGenerator(promise))
return promise;
return fromPromise(promise)();
}
const getIsAsync = quansync({
async: () => Promise.resolve(true),
sync: () => false
});
export { GET_IS_ASYNC, QuansyncError, getIsAsync, quansync, toGenerator };