{"id":20643635,"url":"https://github.com/extrabacon/http-delayed-response","last_synced_at":"2025-06-10T14:06:50.819Z","repository":{"id":14283029,"uuid":"16991104","full_name":"extrabacon/http-delayed-response","owner":"extrabacon","description":"A fast and easy way to delay a response with optional HTTP long-polling, making sure the connection stays alive until the data to send is available. Works with any Node.js HTTP server.","archived":false,"fork":false,"pushed_at":"2019-02-04T14:30:15.000Z","size":152,"stargazers_count":29,"open_issues_count":4,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-16T02:05:27.744Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/extrabacon.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-02-19T16:43:41.000Z","updated_at":"2022-10-11T00:00:44.000Z","dependencies_parsed_at":"2022-09-10T22:24:50.862Z","dependency_job_id":null,"html_url":"https://github.com/extrabacon/http-delayed-response","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extrabacon%2Fhttp-delayed-response","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extrabacon%2Fhttp-delayed-response/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extrabacon%2Fhttp-delayed-response/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extrabacon%2Fhttp-delayed-response/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/extrabacon","download_url":"https://codeload.github.com/extrabacon/http-delayed-response/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249183103,"owners_count":21226141,"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":[],"created_at":"2024-11-16T16:13:28.947Z","updated_at":"2025-04-16T02:05:50.833Z","avatar_url":"https://github.com/extrabacon.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# http-delayed-response\n\nA fast and easy way to delay a response until results are available. Use this module to respond appropriately with status [HTTP 202 Accepted](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success) when the result cannot be determined within an acceptable delay. Supports HTTP long-polling for longer delays, ensuring the connection stays alive until the result is available for working around platform limitations such as error H12 on Heroku or connection errors from aggressive firewalls.\n\nWorks with any Node HTTP server based on [ClientRequest](http://nodejs.org/api/http.html#http_class_http_clientrequest) and [ServerResponse](http://nodejs.org/api/http.html#http_class_http_serverresponse), including Express applications (can be used as standard middleware).\n\nNote: This module is purely experimental and is not ready for production use.\n\n## Installation\n\n```bash\nnpm install http-delayed-response\n```\n\nTo run the tests:\n```bash\nnpm test\n```\n\nThis module has no dependencies.\n\n## Features\n\nFor simplicity, all examples are depicted as Express middleware.\n\n### Waiting for a function to invoke its callback\n\nThis example waits for a slow function indefinitely, rendering its return value into the response. The `wait` method returns a callback that you can use to handle results.\n\n```js\n\nfunction slowFunction (callback) {\n  // let's do something that could take a while...\n}\n\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n  slowFunction(delayed.wait());\n});\n```\n\n### Using promises instead of callbacks\n\nSame thing, except the function returns a promise instead of invoking a callback. Use the `end` method to handle promises.\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n  delayed.wait();\n  var promise = slowFunction();\n  // will eventually end when the promise is fulfilled\n  delayed.end(promise);\n});\n```\n\n### Handling results and timeouts\n\nUse the \"done\" event to handle the response when the function returns successfully within the allocated time. Otherwise, use the \"cancel\" event to handle the response. During a timeout, the response is automatically set to status 202.\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n\n  delayed.on('done', function (results) {\n    // slowFunction responded within 5 seconds\n    res.json(results);\n  }).on('cancel', function () {\n    // slowFunction failed to invoke its callback within 5 seconds\n    // response has been set to HTTP 202\n    res.write('sorry, this will take longer than expected...');\n    res.end();\n  });\n\n  slowFunction(delayed.wait(5000));\n});\n```\n\n### Extended delays and long-polling\n\nIf the function takes even longer to complete, we might face connectivity issues. For example, Heroku aborts the request if not a single byte is written within 30 seconds. To counter this situation, activate long-polling to keep the connection alive while waiting on the results. Use the `start` method instead of `wait` to periodically write non-significant bytes to the response.\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n  // verySlowFunction can now run indefinitely\n  verySlowFunction(delayed.start());\n});\n```\n\nLong-polling is continuously writing spaces (char \\x20) to the response body in order to prevent connection termination. Remember that using long-polling makes handling the response a little different, since HTTP status 202 and headers are already sent to the client.\n\n### Rendering JSON with long-polling\n\nYou are responsible for writing headers before enabling long-polling. If the return value needs to be rendered as JSON, set \"Content-Type\" beforehand, or use the `json` method as a shortcut.\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n  // shortcut for res.setHeader('Content-Type', 'application/json')\n  delayed.json();\n  // start activates long-polling - headers must be set before\n  verySlowFunction(delayed.start());\n});\n```\n\n### Polling a database\n\nWhen long-polling is enabled, use the \"poll\" event to monitor a condition for ending the response. This example polls a MongoDB collection with Mongoose until a particular document is returned. The resulting document is rendered in the response as JSON.\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n\n  delayed.json().on('poll', function () {\n    // \"poll\" event will occur every 5 seconds\n    Model.findOne({ /* criteria */}, function (err, result) {\n      if (err) {\n        // end with an error\n        delayed.end(err);\n      } else if (result) {\n        // end with the resulting document\n        delayed.end(null, result);\n      }\n    });\n  }).start(5000);\n\n});\n```\n\n### Handling the response\n\nBy default, the callback result is rendered into the response body. More precisely:\n  - when returning `null` or `undefined`, the response is ended with no additional content\n  - when returning a `string` or a `Buffer`, it is written as-is\n  - when returning a readable stream, the result is piped into the response\n  - when returning anything else, the result is rendered using `JSON.stringify`\n\nIt is possible to handle the response manually if the default behavior is not appropriate. Be careful: headers are necessarily already sent when the \"done\" handler is called. When handling the response manually, you are responsible for ending it appropriately.\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n\n  delayed.on('done', function (data) {\n    // handle \"data\" anyway you want, but don't forget to end the response!\n    res.end();\n  });\n\n  slowFunction(delayed.wait());\n\n});\n```\n\n### Handling errors\n\nTo handle errors, use the \"error\" event. Otherwise, unhandled errors will be thrown. Timeouts that are not handled with a \"cancel\" event are treated like normal errors. When using long-polling, HTTP status 202 is already applied and the HTTP protocol has no mechanism to indicate an error past this point. Also, when handling errors, you are responsible for ending the response.\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n\n  delayed.on('error', function (err) {\n    // handle error here\n    // timeout will also raise an error since there is no \"cancel\" handler\n  });\n\n  slowFunction(delayed.wait(5000));\n\n});\n```\n\nErrors can also be handled with Connect or Express middleware by supplying the `next` parameter to the constructor.\n\n```js\napp.use(function (req, res, next) {\n  var delayed = new DelayedResponse(req, res, next);\n  // \"next\" will be invoked if \"slowFunction\" fails or take longer than 1 second to return\n  slowFunction(delayed.wait(1000));\n});\n```\n\n### Handling aborted requests\n\nBy default, a response is ended with no additional content if the client aborts the request before completion. If you need to handle an aborted request, attach the \"abort\" event. When handling client disconnects, you are responsible for ending the response.\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n\n  delayed.on('abort', function (err) {\n    // handle client disconnection\n    res.end();\n  });\n\n  // wait indefinitely - client might get bored...\n  slowFunction(delayed.wait());\n\n});\n```\n\n### Keeping the connection alive with long-polling\n\nBy default, when using long-polling, the connection is kept alive by writing a single space to the response at the specified interval (default is 100msec).\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n  // write a \"\\x20\" every second, until function is completed\n  verySlowFunction(delayed.start(1000));\n});\n```\n\nAn initial delay before the first byte can also be specified (default is also 100msec).\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n  // write a \"\\x20\" every second after 10 seconds, until function is completed\n  verySlowFunction(delayed.start(1000, 10000));\n});\n```\n\nTo avoid H12 errors in Heroku, initial delay must be under 30 seconds and at least 1 byte must be written every 55 seconds. See https://devcenter.heroku.com/articles/request-timeout for more details.\n\nTo manually keep the connection alive, attach the \"heartbeat\" event.\n\n```js\napp.use(function (req, res) {\n  var delayed = new DelayedResponse(req, res);\n  delayed.on('heartbeat', function () {\n    // anything you need to do to keep the connection alive\n  });\n  verySlowFunction(delayed.start(1000));\n});\n```\n\n## API Reference\n\n#### DelayedResponse(req, res, next)\n\nCreates a `DelayedResponse` instance. Parameters represent the usual middleware signature.\n\n#### DelayedResponse.wait(timeout)\n\nReturns a callback handler that must be invoked within the allocated time represented by `timeout`.\n\nThe returned handler is the same as calling `DelayedResponse.end`.\n\n#### DelayedResponse.start(interval, initialDelay, timeout)\n\nStarts long-polling for the delayed response, sending headers and HTTP status 202.\n\nPolling will occur at the specified `interval`, starting after `initialDelay`.\n\nReturns a callback handler, same as `DelayedResponse.end`.\n\n#### DelayedResponse.end(err, data)\n\nStops waiting, sending the contents represented by `data` in the response - or invoke the error handler if an error is present.\n\n#### DelayedResponse.stop()\n\nStops monitoring timers without affecting the response.\n\n#### DelayedResponse.json()\n\nShortcut for setting the \"Content-Type\" header to \"application/json\". Returns itself for chaining calls.\n\n#### Event: 'done'\n\nFired when `end` is invoked without an error. If this event is not handled, the callback result is written in the response.\n\n#### Event: 'error'\n\nFired when `end` is invoked with an error. If this event is not handled, the error is thrown as an uncaught error.\n\n#### Event: 'cancel'\n\nFired when `end` failed to be invoked within the allocated time. If this event is not handled, the timeout is considered a normal error that can be handled using the `error` event.\n\n#### Event: 'abort'\n\nFired when the request is closed.\n\n#### Event: 'poll'\n\nFired continuously at the specified interval when invoking `start`.\n\n#### Event: 'heartbeat'\n\nFired continuously at the specified interval when invoking `start`. Can be used to override the \"keep-alive\" mechanism.\n\n## Compatibility\n\n+ Tested with Node 0.10.x\n+ Tested on Mac OS X 10.8\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2013, Nicolas Mercier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextrabacon%2Fhttp-delayed-response","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fextrabacon%2Fhttp-delayed-response","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextrabacon%2Fhttp-delayed-response/lists"}