diff --git a/package.json b/package.json new file mode 100644 index 0000000..50e4097 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "ig-stoppable", + "version": "1.0.0", + "description": "Utility library implementing tooling to make stoppable functions...", + "main": "stoppable.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/flynx/stoppable.js.git" + }, + "author": "Alex A. Naanou", + "license": "BSD-3-Clause", + "bugs": { + "url": "https://github.com/flynx/stoppable.js/issues" + }, + "homepage": "https://github.com/flynx/stoppable.js#readme" +} diff --git a/stoppable.js b/stoppable.js new file mode 100644 index 0000000..95ebe06 --- /dev/null +++ b/stoppable.js @@ -0,0 +1,144 @@ +/********************************************************************** +* +* stoppable.js +* +* Utility library implementing tooling to make stoppable functions... +* +* +* Repo and docs: +* https://github.com/flynx/stoppable.js +* +* +***********************************************/ /* c8 ignore next 2 */ +((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) +(function(require){ var module={} // make module AMD/node compatible... +/*********************************************************************/ + + +//--------------------------------------------------------------------- +// helpers... + +var Generator = + (function*(){}).constructor + +var AsyncGenerator = + (async function*(){}).constructor + + + +//--------------------------------------------------------------------- + +var STOP = +module.STOP = +function(value){ + return { + __proto__: STOP.prototype, + doc: 'stop iteration.', + value, + } } + + + +//--------------------------------------------------------------------- + +// Wrap a callable in a STOP handler +// +// stoppable(func) +// -> func +// +// stoppable(gen) +// -> gen +// +// stoppable(asyncgen) +// -> asyncgen +// +// +// The client callable can be one of: +// - function +// - generator +// - async generator +// +// The returned callable will be of the same type as the input callable. +// +// The wrapper handles STOP slightly differently if the client is a +// function or if it is a generator / async generator: +// - function +// STOP returned / thrown +// -> return undefined +// STOP(value) returned / thrown +// -> return value +// - generator / async generator +// STOP yielded / thrown +// -> iteration stops +// STOP(value) yielded / thrown +// -> value yielded and iteration stops +// +// +// NOTE: this repeats the same code at lest twice, not sure yet how to avoid +// this... +var stoppable = +module.stoppable = +function(func){ + return Object.assign( + func instanceof Generator ? + // NOTE: the only difference between Generator/AsyncGenerator + // versions of this is the async keyword -- keep them + // in sync... + function*(){ + try{ + for(var res of func.call(this, ...arguments)){ + if(res === module.STOP){ + return } + if(res instanceof module.STOP){ + yield res.value + return } + yield res } + } catch(err){ + if(err === module.STOP){ + return + } else if(err instanceof module.STOP){ + yield err.value + return } + throw err } } + : func instanceof AsyncGenerator ? + // NOTE: the only difference between Generator/AsyncGenerator + // versions of this is the async keyword -- keep them + // in sync... + async function*(){ + try{ + for(var res of func.call(this, ...arguments)){ + if(res === module.STOP){ + return } + if(res instanceof module.STOP){ + yield res.value + return } + yield res } + } catch(err){ + if(err === module.STOP){ + return + } else if(err instanceof module.STOP){ + yield err.value + return } + throw err } } + : function(){ + try{ + var res = func.call(this, ...arguments) + // NOTE: this is here for uniformity... + if(res === module.STOP){ + return } + if(res instanceof module.STOP){ + return res.value } + return res + } catch(err){ + if(err === module.STOP){ + return + } else if(err instanceof module.STOP){ + return err.value } + throw err } }, + { toString: function(){ + return func.toString() }, }) } + + + +/********************************************************************** +* vim:set ts=4 sw=4 nowrap : */ return module })