sourav-das/stem-separator
3
1"use strict";2 3// These use the global symbol registry so that multiple copies of this4// library can work together in case they are not deduped.5const GENSYNC_START = Symbol.for("gensync:v1:start");6const GENSYNC_SUSPEND = Symbol.for("gensync:v1:suspend");7 8const GENSYNC_EXPECTED_START = "GENSYNC_EXPECTED_START";9const GENSYNC_EXPECTED_SUSPEND = "GENSYNC_EXPECTED_SUSPEND";10const GENSYNC_OPTIONS_ERROR = "GENSYNC_OPTIONS_ERROR";11const GENSYNC_RACE_NONEMPTY = "GENSYNC_RACE_NONEMPTY";12const GENSYNC_ERRBACK_NO_CALLBACK = "GENSYNC_ERRBACK_NO_CALLBACK";13 14module.exports = Object.assign(15 function gensync(optsOrFn) {16 let genFn = optsOrFn;17 if (typeof optsOrFn !== "function") {18 genFn = newGenerator(optsOrFn);19 } else {20 genFn = wrapGenerator(optsOrFn);21 }22 23 return Object.assign(genFn, makeFunctionAPI(genFn));24 },25 {26 all: buildOperation({27 name: "all",28 arity: 1,29 sync: function(args) {30 const items = Array.from(args[0]);31 return items.map(item => evaluateSync(item));32 },33 async: function(args, resolve, reject) {34 const items = Array.from(args[0]);35 36 if (items.length === 0) {37 Promise.resolve().then(() => resolve([]));38 return;39 }40 41 let count = 0;42 const results = items.map(() => undefined);43 items.forEach((item, i) => {44 evaluateAsync(45 item,46 val => {47 results[i] = val;48 count += 1;49 50 if (count === results.length) resolve(results);51 },52 reject53 );54 });55 },56 }),57 race: buildOperation({58 name: "race",59 arity: 1,60 sync: function(args) {61 const items = Array.from(args[0]);62 if (items.length === 0) {63 throw makeError("Must race at least 1 item", GENSYNC_RACE_NONEMPTY);64 }65 66 return evaluateSync(items[0]);67 },68 async: function(args, resolve, reject) {69 const items = Array.from(args[0]);70 if (items.length === 0) {71 throw makeError("Must race at least 1 item", GENSYNC_RACE_NONEMPTY);72 }73 74 for (const item of items) {75 evaluateAsync(item, resolve, reject);76 }77 },78 }),79 }80);81 82/**83 * Given a generator function, return the standard API object that executes84 * the generator and calls the callbacks.85 */86function makeFunctionAPI(genFn) {87 const fns = {88 sync: function(...args) {89 return evaluateSync(genFn.apply(this, args));90 },91 async: function(...args) {92 return new Promise((resolve, reject) => {93 evaluateAsync(genFn.apply(this, args), resolve, reject);94 });95 },96 errback: function(...args) {97 const cb = args.pop();98 if (typeof cb !== "function") {99 throw makeError(100 "Asynchronous function called without callback",101 GENSYNC_ERRBACK_NO_CALLBACK102 );103 }104 105 let gen;106 try {107 gen = genFn.apply(this, args);108 } catch (err) {109 cb(err);110 return;111 }112 113 evaluateAsync(gen, val => cb(undefined, val), err => cb(err));114 },115 };116 return fns;117}118 119function assertTypeof(type, name, value, allowUndefined) {120 if (121 typeof value === type ||122 (allowUndefined && typeof value === "undefined")123 ) {124 return;125 }126 127 let msg;128 if (allowUndefined) {129 msg = `Expected opts.${name} to be either a ${type}, or undefined.`;130 } else {131 msg = `Expected opts.${name} to be a ${type}.`;132 }133 134 throw makeError(msg, GENSYNC_OPTIONS_ERROR);135}136function makeError(msg, code) {137 return Object.assign(new Error(msg), { code });138}139 140/**141 * Given an options object, return a new generator that dispatches the142 * correct handler based on sync or async execution.143 */144function newGenerator({ name, arity, sync, async, errback }) {145 assertTypeof("string", "name", name, true /* allowUndefined */);146 assertTypeof("number", "arity", arity, true /* allowUndefined */);147 assertTypeof("function", "sync", sync);148 assertTypeof("function", "async", async, true /* allowUndefined */);149 assertTypeof("function", "errback", errback, true /* allowUndefined */);150 if (async && errback) {151 throw makeError(152 "Expected one of either opts.async or opts.errback, but got _both_.",153 GENSYNC_OPTIONS_ERROR154 );155 }156 157 if (typeof name !== "string") {158 let fnName;159 if (errback && errback.name && errback.name !== "errback") {160 fnName = errback.name;161 }162 if (async && async.name && async.name !== "async") {163 fnName = async.name.replace(/Async$/, "");164 }165 if (sync && sync.name && sync.name !== "sync") {166 fnName = sync.name.replace(/Sync$/, "");167 }168 169 if (typeof fnName === "string") {170 name = fnName;171 }172 }173 174 if (typeof arity !== "number") {175 arity = sync.length;176 }177 178 return buildOperation({179 name,180 arity,181 sync: function(args) {182 return sync.apply(this, args);183 },184 async: function(args, resolve, reject) {185 if (async) {186 async.apply(this, args).then(resolve, reject);187 } else if (errback) {188 errback.call(this, ...args, (err, value) => {189 if (err == null) resolve(value);190 else reject(err);191 });192 } else {193 resolve(sync.apply(this, args));194 }195 },196 });197}198 199function wrapGenerator(genFn) {200 return setFunctionMetadata(genFn.name, genFn.length, function(...args) {201 return genFn.apply(this, args);202 });203}204 205function buildOperation({ name, arity, sync, async }) {206 return setFunctionMetadata(name, arity, function*(...args) {207 const resume = yield GENSYNC_START;208 if (!resume) {209 // Break the tail call to avoid a bug in V8 v6.X with --harmony enabled.210 const res = sync.call(this, args);211 return res;212 }213 214 let result;215 try {216 async.call(217 this,218 args,219 value => {220 if (result) return;221 222 result = { value };223 resume();224 },225 err => {226 if (result) return;227 228 result = { err };229 resume();230 }231 );232 } catch (err) {233 result = { err };234 resume();235 }236 237 // Suspend until the callbacks run. Will resume synchronously if the238 // callback was already called.239 yield GENSYNC_SUSPEND;240 241 if (result.hasOwnProperty("err")) {242 throw result.err;243 }244 245 return result.value;246 });247}248 249function evaluateSync(gen) {250 let value;251 while (!({ value } = gen.next()).done) {252 assertStart(value, gen);253 }254 return value;255}256 257function evaluateAsync(gen, resolve, reject) {258 (function step() {259 try {260 let value;261 while (!({ value } = gen.next()).done) {262 assertStart(value, gen);263 264 // If this throws, it is considered to have broken the contract265 // established for async handlers. If these handlers are called266 // synchronously, it is also considered bad behavior.267 let sync = true;268 let didSyncResume = false;269 const out = gen.next(() => {270 if (sync) {271 didSyncResume = true;272 } else {273 step();274 }275 });276 sync = false;277 278 assertSuspend(out, gen);279 280 if (!didSyncResume) {281 // Callback wasn't called synchronously, so break out of the loop282 // and let it call 'step' later.283 return;284 }285 }286 287 return resolve(value);288 } catch (err) {289 return reject(err);290 }291 })();292}293 294function assertStart(value, gen) {295 if (value === GENSYNC_START) return;296 297 throwError(298 gen,299 makeError(300 `Got unexpected yielded value in gensync generator: ${JSON.stringify(301 value302 )}. Did you perhaps mean to use 'yield*' instead of 'yield'?`,303 GENSYNC_EXPECTED_START304 )305 );306}307function assertSuspend({ value, done }, gen) {308 if (!done && value === GENSYNC_SUSPEND) return;309 310 throwError(311 gen,312 makeError(313 done314 ? "Unexpected generator completion. If you get this, it is probably a gensync bug."315 : `Expected GENSYNC_SUSPEND, got ${JSON.stringify(316 value317 )}. If you get this, it is probably a gensync bug.`,318 GENSYNC_EXPECTED_SUSPEND319 )320 );321}322 323function throwError(gen, err) {324 // Call `.throw` so that users can step in a debugger to easily see which325 // 'yield' passed an unexpected value. If the `.throw` call didn't throw326 // back to the generator, we explicitly do it to stop the error327 // from being swallowed by user code try/catches.328 if (gen.throw) gen.throw(err);329 throw err;330}331 332function isIterable(value) {333 return (334 !!value &&335 (typeof value === "object" || typeof value === "function") &&336 !value[Symbol.iterator]337 );338}339 340function setFunctionMetadata(name, arity, fn) {341 if (typeof name === "string") {342 // This should always work on the supported Node versions, but for the343 // sake of users that are compiling to older versions, we check for344 // configurability so we don't throw.345 const nameDesc = Object.getOwnPropertyDescriptor(fn, "name");346 if (!nameDesc || nameDesc.configurable) {347 Object.defineProperty(348 fn,349 "name",350 Object.assign(nameDesc || {}, {351 configurable: true,352 value: name,353 })354 );355 }356 }357 358 if (typeof arity === "number") {359 const lengthDesc = Object.getOwnPropertyDescriptor(fn, "length");360 if (!lengthDesc || lengthDesc.configurable) {361 Object.defineProperty(362 fn,363 "length",364 Object.assign(lengthDesc || {}, {365 configurable: true,366 value: arity,367 })368 );369 }370 }371 372 return fn;373}374 