{"id":19014287,"url":"https://github.com/xkjyeah/bypass-stack","last_synced_at":"2026-05-14T18:33:51.370Z","repository":{"id":66183636,"uuid":"86089769","full_name":"xkjyeah/bypass-stack","owner":"xkjyeah","description":"How to bypass the Node.js call stack limit","archived":false,"fork":false,"pushed_at":"2017-03-25T15:56:10.000Z","size":5,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-01T22:29:17.848Z","etag":null,"topics":["node","stack","tail-call-optimization"],"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/xkjyeah.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-24T16:39:58.000Z","updated_at":"2023-04-21T01:54:06.000Z","dependencies_parsed_at":"2023-02-26T19:01:35.713Z","dependency_job_id":null,"html_url":"https://github.com/xkjyeah/bypass-stack","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/xkjyeah%2Fbypass-stack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xkjyeah%2Fbypass-stack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xkjyeah%2Fbypass-stack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xkjyeah%2Fbypass-stack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xkjyeah","download_url":"https://codeload.github.com/xkjyeah/bypass-stack/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240052972,"owners_count":19740604,"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":["node","stack","tail-call-optimization"],"created_at":"2024-11-08T19:28:43.626Z","updated_at":"2025-11-10T18:31:09.637Z","avatar_url":"https://github.com/xkjyeah.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bypassing Node's call stack limit with `async`/`await`\r\n\r\nTL;DR With `async`/`await`, you can unroll your recursive functions onto the\r\nJavascript event loop!\r\n\r\nThe following piece of code will die a terrible death:\r\n\r\n```js\r\nfunction sumOf1To(n) {\r\n  if (n == 0) {\r\n    return 0\r\n  }\r\n\r\n  return n + sumOf1To(n - 1)\r\n}\r\n\r\nconsole.log(sumOf1To(100000))\r\n```\r\n\r\nOutput:\r\n```\r\nC:\\Users\\Daniel\\Desktop\\test\u003enode sync.js\r\nC:\\Users\\Daniel\\Desktop\\test\\sync.js:2\r\nfunction sumOf1To(n) {\r\n                 ^\r\n\r\nRangeError: Maximum call stack size exceeded\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:2:18)\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:7:14)\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:7:14)\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:7:14)\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:7:14)\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:7:14)\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:7:14)\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:7:14)\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:7:14)\r\n    at sumOf1To (C:\\Users\\Daniel\\Desktop\\test\\sync.js:7:14)\r\n\r\nC:\\Users\\Daniel\\Desktop\\test\u003e\r\n```\r\n\r\nThis is because the computer's stack is limited. However, what if we could move\r\nthe function call stack to the heap? That is what `async`/`await` does.\r\nIn fact, if you look at how Babel transpiles `async`/`await` you will notice\r\nthat they store what were once local variables as properties of an (heap-allocated)\r\nobject.\r\n\r\nSo let's try:\r\n\r\n```js\r\n\r\nasync function sumOf1To(n) {\r\n  if (n == 0) {\r\n    return 0\r\n  }\r\n\r\n  return n + await sumOf1To(n - 1)\r\n}\r\n\r\nsumOf1To(100000)\r\n.then(sum =\u003e console.log(sum))\r\n.catch((err) =\u003e console.log(err.stack))\r\n```\r\n\r\nViola!\r\n\r\n```\r\nC:\\Users\\Daniel\\Desktop\\test\u003enode async.js\r\n(node:8644) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): RangeError: Maximum call stack size exceeded\r\n(node:8644) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.\r\n(node:8644) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): RangeError: Maximum call stack size exceeded\r\n(node:8644) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): RangeError: Maximum call stack size exceeded\r\n...\r\n```\r\n\r\nOoops. Something went wrong here. It turns out that we were not bypassing the\r\nstack, because up to the final `await` statement all the calls were still synchronous.\r\n\r\nA little `await` no-op fixes it:\r\n\r\n```js\r\nasync function sumOf1To(n) {\r\n  if (n == 0) {\r\n    return 0\r\n  }\r\n\r\n  await Promise.resolve(0) // no-op\r\n\r\n  return n + await sumOf1To(n - 1)\r\n}\r\n\r\nsumOf1To(100000)\r\n.then(sum =\u003e console.log(sum))\r\n.catch((err) =\u003e console.log(err.stack))\r\n```\r\n\r\nSuccess!\r\n```\r\nC:\\Users\\Daniel\\Desktop\\test\u003enode transpiled.js\r\n5000050000\r\n\r\nC:\\Users\\Daniel\\Desktop\\test\u003e\r\n```\r\n\r\n## Conclusion\r\n\r\nIf you are a Javascript fan, a functional programming afficiando, a\r\nlover of recursive functions, and annoyed that Javascript doesn't do tail-call\r\noptimization (or don't know what that means) and don't mind a (fairly significant)\r\nperformance penalty, or using up a lot of memory on the heap,\r\nconsider using `async`/`await`!\r\n\r\n-----------------\r\n\r\nThe above was tested with Node 7.7.4, with both native `async`/`await` and\r\nBabel-transpiled code.\r\n\r\n# Appendix (a look at how the no-op changes recursion semantics)\r\n\r\n**The failure** (comments added):\r\n```js\r\nwhile (1) {\r\n  switch (_context.prev = _context.next) {\r\n    case 0:\r\n      if (!(n == 0)) {\r\n        _context.next = 2;\r\n        break; // Immediately jumps to case 2\r\n      }\r\n\r\n      return _context.abrupt(\"return\", 0);\r\n\r\n    case 2:\r\n      _context.t0 = n;\r\n      _context.next = 5;\r\n      return sumOf1To(n - 1); // Stack recursion!\r\n```\r\n\r\n**The success** (comments added)\r\n```js\r\nwhile (1) {\r\n  switch (_context.prev = _context.next) {\r\n    case 0:\r\n      if (!(n == 0)) {\r\n        _context.next = 2;\r\n        break;\r\n      }\r\n\r\n      return _context.abrupt(\"return\", 0);\r\n\r\n    case 2:\r\n      _context.next = 4;\r\n      return _promise2.default.resolve(0); // Suspends function execution, pushes the context to the event loop\r\n\r\n    case 4:\r\n      // When this is executed again, it's called straight\r\n      // from the event loop -- no more stack to worry about!\r\n      _context.t0 = n;\r\n      _context.next = 7;\r\n      return sumOf1To(n - 1);\r\n  ```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxkjyeah%2Fbypass-stack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxkjyeah%2Fbypass-stack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxkjyeah%2Fbypass-stack/lists"}