index.mjs 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. const GET_IS_ASYNC = Symbol.for("quansync.getIsAsync");
  2. class QuansyncError extends Error {
  3. constructor(message = "Unexpected promise in sync context") {
  4. super(message);
  5. this.name = "QuansyncError";
  6. }
  7. }
  8. function isThenable(value) {
  9. return value && typeof value === "object" && typeof value.then === "function";
  10. }
  11. function isQuansyncGenerator(value) {
  12. return value && typeof value === "object" && typeof value[Symbol.iterator] === "function" && "__quansync" in value;
  13. }
  14. function fromObject(options) {
  15. const generator = function* (...args) {
  16. const isAsync = yield GET_IS_ASYNC;
  17. if (isAsync)
  18. return yield options.async.apply(this, args);
  19. return options.sync.apply(this, args);
  20. };
  21. function fn(...args) {
  22. const iter = generator.apply(this, args);
  23. iter.then = (...thenArgs) => options.async.apply(this, args).then(...thenArgs);
  24. iter.__quansync = true;
  25. return iter;
  26. }
  27. fn.sync = options.sync;
  28. fn.async = options.async;
  29. return fn;
  30. }
  31. function fromPromise(promise) {
  32. return fromObject({
  33. async: () => Promise.resolve(promise),
  34. sync: () => {
  35. if (isThenable(promise))
  36. throw new QuansyncError();
  37. return promise;
  38. }
  39. });
  40. }
  41. function unwrapYield(value, isAsync) {
  42. if (value === GET_IS_ASYNC)
  43. return isAsync;
  44. if (isQuansyncGenerator(value))
  45. return isAsync ? iterateAsync(value) : iterateSync(value);
  46. if (!isAsync && isThenable(value))
  47. throw new QuansyncError();
  48. return value;
  49. }
  50. const DEFAULT_ON_YIELD = (value) => value;
  51. function iterateSync(generator, onYield = DEFAULT_ON_YIELD) {
  52. let current = generator.next();
  53. while (!current.done) {
  54. try {
  55. current = generator.next(unwrapYield(onYield(current.value, false)));
  56. } catch (err) {
  57. current = generator.throw(err);
  58. }
  59. }
  60. return unwrapYield(current.value);
  61. }
  62. async function iterateAsync(generator, onYield = DEFAULT_ON_YIELD) {
  63. let current = generator.next();
  64. while (!current.done) {
  65. try {
  66. current = generator.next(await unwrapYield(onYield(current.value, true), true));
  67. } catch (err) {
  68. current = generator.throw(err);
  69. }
  70. }
  71. return current.value;
  72. }
  73. function fromGeneratorFn(generatorFn, options) {
  74. return fromObject({
  75. name: generatorFn.name,
  76. async(...args) {
  77. return iterateAsync(generatorFn.apply(this, args), options?.onYield);
  78. },
  79. sync(...args) {
  80. return iterateSync(generatorFn.apply(this, args), options?.onYield);
  81. }
  82. });
  83. }
  84. function quansync(input, options) {
  85. if (isThenable(input))
  86. return fromPromise(input);
  87. if (typeof input === "function")
  88. return fromGeneratorFn(input, options);
  89. else
  90. return fromObject(input);
  91. }
  92. function toGenerator(promise) {
  93. if (isQuansyncGenerator(promise))
  94. return promise;
  95. return fromPromise(promise)();
  96. }
  97. const getIsAsync = quansync({
  98. async: () => Promise.resolve(true),
  99. sync: () => false
  100. });
  101. export { GET_IS_ASYNC, QuansyncError, getIsAsync, quansync, toGenerator };