{"id":19574701,"url":"https://github.com/stringparser/runtime","last_synced_at":"2025-04-27T06:30:32.791Z","repository":{"id":19408767,"uuid":"22650772","full_name":"stringparser/runtime","owner":"stringparser","description":"a runtime interface","archived":false,"fork":false,"pushed_at":"2017-02-12T11:16:20.000Z","size":196,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-04-28T14:44:48.568Z","etag":null,"topics":["async","callback","compose","nodejs","promise","stream"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stringparser.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-08-05T16:30:35.000Z","updated_at":"2017-02-12T11:11:38.000Z","dependencies_parsed_at":"2022-08-20T22:20:12.660Z","dependency_job_id":null,"html_url":"https://github.com/stringparser/runtime","commit_stats":null,"previous_names":["stringparser/tornado"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stringparser%2Fruntime","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stringparser%2Fruntime/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stringparser%2Fruntime/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stringparser%2Fruntime/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stringparser","download_url":"https://codeload.github.com/stringparser/runtime/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224062767,"owners_count":17249289,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","callback","compose","nodejs","promise","stream"],"created_at":"2024-11-11T06:43:31.130Z","updated_at":"2024-11-11T06:43:32.713Z","avatar_url":"https://github.com/stringparser.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# runtime [![NPM version][badge-version]][x-npm][![downloads][badge-downloads]][x-npm]\n[![Build status][badge-build]][x-travis]\n\n[breaking changes](#breaking-changes) -\n[documentation](#documentation) -\n[examples](#examples) -\n[install](#install) -\n[todo](#todo) -\n[why](#why)\n\nThe aim of the project is to compose asynchronous functions and provide a basic api to create an interface around them. It is for people who hate so many choices around the same problem (i.e. callbacks, promises, streams, ...)\n\nOnce these asynchronous functions are composed, they are not executed right away. Instead another function is returned leaving execution of this `stack` to the writer. This function can be used multiple times.\n\nNote that every function is made asynchronous and should be resolved either with a callback, returning a stream, a promise or a [RxJS observable][RxJS-observable].\n\n## usage\n\nAs an example let's make 3 async functions. One using a callback, other returning a promise and another a stream.\n\n```js\nvar fs = require('fs');\nvar through = require('through2');\nvar Promise = require('es6-promise').Promise;\n\nfunction foo (next, value) {\n  console.log('received `%s`', value);\n  setTimeout(function () {\n    next(null, 'Callback');\n  }, Math.random() * 10);\n}\n\nfunction bar (next, value) {\n  return new Promise(function (resolve) {\n    setTimeout(function () {\n      resolve(value + 'Promise');\n    }, Math.random() * 10);\n  });\n}\n\nfunction baz (next, value) {\n  var stream = fs.createReadStream(__filename);\n\n  return stream.once('end', function () {\n    next(null, value + 'Stream');\n  });\n}\n```\n\nAll right we have 3 functions. Lets setup an interface around them. For the sake of simplicity lets make a logger with error handling.\n\n```js\nvar Runtime = require('runtime');\n\n// create a composer class that will have what we need\nvar Composer = Runtime.createClass({\n  reduceStack: function (stack, site) {\n    if(typeof site === 'function'){\n      stack.push({\n        fn: site,\n        label: Array.isArray(site.stack)\n          ? this.tree(site.stack).label\n          : site.label || site.name || 'anonymous'\n      });\n    }\n  },\n  onHandleStart: function (site, stack) {\n    console.log('`%s` started', site.label);\n    site.time = process.hrtime();\n  },\n  onHandleEnd: function (site, stack) {\n    var diff = process.hrtime(site.time);\n    console.log('`%s` ended after %s ms',\n      site.label, diff[0]*1e+3 + Math.floor(diff[1]*1e-6)\n    );\n  },\n  onHandleError: function (error, site) {\n    var file = error.stack.match(/\\/[^)]+/).pop();\n    console.log('`%s` errored at', site.label, file);\n    console.log(error.stack);\n  }\n});\n```\n\nNow let's compose those into _one_ asynchronous function using\nthis brand new `runtime` instance we have created.\n\nHow does it look like?\n\nThe default goes like this: last argument for options, all the others for functions.\n\n\n```js\n// create a Composer instance\nvar runtime = Composer.create();\n\nvar composed = runtime.stack(foo, bar, baz, { wait: true });\n// runtime.stack will run each site in parallel by default\n// to change it pass `{ wait: true }` and each site will run in series\n\n// lets make it pretty\nconsole.log('Stack tree -\u003e %s',\n  require('archy')(runtime.tree(composed.stack))\n);\n\n// use it just as normal async function\ncomposed('insert args here', function done(error, result){\n  if (error) {\n    console.log(err.stack);\n  } else {\n    console.log('result: `%s`', result);\n  }\n});\n```\n\nHere we go. This is the output logged.\n\n```sh\nStack tree -\u003e foo, bar, baz\n├── foo\n├── bar\n└── baz\n\n`foo` started\nreceived `insert args here`\n`foo` ended after 3 ms\n`bar` started\n`bar` ended after 3 ms\n`baz` started\n`baz` ended after 7 ms\nresult: `CallbackPromiseStream`\n```\n\n## documentation\n\nWork in progress.\n\n## install\n\nWith [npm](http://npmjs.org)\n\n    npm install --save runtime\n\n## breaking changes\n\nIf you where using a previous version, the internals have been cleaned and simplified a lot to offer the same idea with less opinions and more reuse.\n\nNow `runtime.stack` composes only functions **by default**. If you want to\ngive strings that then are mapped to a function, that is, you want to write\n\n```js\nvar composed = runtime.stack('foo', 'bar');\n```\nyou will have to use the following approach\n\n```js\nvar Runtime = require('runtime');\n\n// create a class\nvar RuntimeClass = Runtime.createClass({\n  create: function () {\n    this.tasks = {};\n  },\n  task: function (name, handle) {\n    if (typeof name !== 'string') {\n      throw new TypeError('`name` should be a string');\n    } else if (typeof handle !== 'function') {\n      throw new TypeError('`handle` should be a function');\n    }\n\n    this.tasks[name] = handle;\n    return this;\n  },\n  // similar to Array.prototype.reduce with an empty array\n  // given for the for the previous argument (stack = [] on first call)\n  reduceStack: function (stack, site) {\n    if (typeof site === 'string' \u0026\u0026 typeof this.tasks[site] === 'function') {\n      stack.push(this.tasks[site]);\n    } else if (typeof site === 'function') {\n      stack.push(site);\n    }\n  }\n});\n\n// instantiate\nvar runtime = RuntimeClass.create();\n\n// register your mapping from string to function\nruntime.task('one', function handleOne (next, myArg) {\n  // do async things\n  next(); // or return a  promise, stream or RxJS observable\n});\n\nfunction two (next, myArg) {\n  // do async things\n  next(); // or return a  promise, stream or RxJS observable\n}\n\n// now you can `stack` functions and strings together\nvar composer = runtime.stack('one', two);\n\n// run the `stack` function returned\ncomposer('myArg', function onStackEnd (err, result) {\n  if (err) { throw err; }\n  console.log(result);\n});\n```\n\n### test\n\n```\n➜  runtime (master) ✔ npm test\n\napi\n  ✓ onHandleStart is called before each site\n  ✓ onHandleEnd is called before each site\n  ✓ nested: onHandleStart is called before and after each site\n  ✓ nested: onHandleEnd is called before and after each site\n  ✓ context for each stack can be given {context: [Object]}\n  ✓ can be reused with no side-effects\n  ✓ create({wait: true}) makes all stacks wait\n\nexports\n  ✓ create() should return a new instance\n  ✓ create(object mixin) should add to the instance properties\n  ✓ createClass() should return a new constructor\n  ✓ createClass(object mixin) mixin with new constructor\n  ✓ createClass({create: [Function]}) should be used as ctor\n\nstack-callbacks\n  ✓ uses the callback when a fn throws\n  ✓ uses the callback when passes the error\n  ✓ passes error to onHandleError when no callback given\n  ✓ runs the callback on completion\n  ✓ runs fns in parallel by default\n  ✓ {wait: true} should run functions in series\n  ✓ passes arguments when fns wait\n  ✓ does NOT pass arguments when fns does NOT wait\n\nstack-promises\n  ✓ uses the callback when a promise throws\n  ✓ uses the callback when promises rejects\n  ✓ passes error to onHandleError if no callback was given\n  ✓ runs the callback after completion of all promises\n  ✓ runs in parallel by default\n  ✓ runs in series with {wait: true}\n  ✓ passes arguments when it waits\n  ✓ does NOT pass arguments when fns does NOT wait\n\nstack-streams\n  ✓ uses the callback when a stream throws an error\n  ✓ uses the callback when a stream emits an error\n  ✓ passes error to onHandleError if no callback was given\n  ✓ runs the callback after completion of all streams\n  ✓ runs in parallel by default\n  ✓ runs in series with {wait: true}\n\nstacks-composed\n  ✓ runs callback if fn throws from other stack\n  ✓ runs callback if error given to next from other stack\n  ✓ runs the callback on completion of all stacks\n  ✓ runs stacks in parallel by default\n  ✓ {wait: true} should run stacks in series\n  ✓ series: callback is run after all stacks are finished\n  ✓ passes arguments when host and completed stack waits\n  ✓ does NOT pass arguments when stacks does NOT wait\n\n\n42 passing (229ms)\n```\n\n## why\n\nThere are several ways to manage complexity for asynchronous functions.\nOnes are better than others for some use-cases and sometimes with callbacks\nis more than enough. But we all want to avoid callback hell and reuse as much\nas possible.\n\n### todo\n - [ ] be able to redo or rewind within the same stack\n\n### license\n\nThe MIT License (MIT)\n\nCopyright (c) 2015-present Javier Carrillo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\u003c!-- links --\u003e\n\n[x-npm]: https://npmjs.org/package/runtime\n[x-travis]: https://travis-ci.org/stringparser/runtime\n[async-done]: https://github.com/gulpjs/async-done\n\n[badge-build]: http://img.shields.io/travis/stringparser/runtime/master.svg?style=flat-square\n[badge-version]: http://img.shields.io/npm/v/runtime.svg?style=flat-square\n[badge-downloads]: http://img.shields.io/npm/dm/runtime.svg?style=flat-square\n\n[RxJS-observable]: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstringparser%2Fruntime","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstringparser%2Fruntime","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstringparser%2Fruntime/lists"}