{"id":13467418,"url":"https://github.com/aantron/promise","last_synced_at":"2026-02-03T15:04:03.370Z","repository":{"id":55960401,"uuid":"116451935","full_name":"aantron/promise","owner":"aantron","description":"Light and type-safe binding to JS promises","archived":false,"fork":false,"pushed_at":"2023-10-04T14:07:58.000Z","size":394,"stargazers_count":341,"open_issues_count":16,"forks_count":25,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-11-22T22:03:23.760Z","etag":null,"topics":["async","promises","rescript"],"latest_commit_sha":null,"homepage":"","language":"Reason","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/aantron.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":"aantron"}},"created_at":"2018-01-06T04:03:58.000Z","updated_at":"2025-11-12T15:13:03.000Z","dependencies_parsed_at":"2024-01-06T07:44:15.549Z","dependency_job_id":"f90c6a36-2208-4d15-94d0-bbe8a2466263","html_url":"https://github.com/aantron/promise","commit_stats":{"total_commits":273,"total_committers":12,"mean_commits":22.75,"dds":0.06959706959706957,"last_synced_commit":"ffe995c431c7620362814907773fd44ce17cf972"},"previous_names":["aantron/repromise"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/aantron/promise","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aantron%2Fpromise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aantron%2Fpromise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aantron%2Fpromise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aantron%2Fpromise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aantron","download_url":"https://codeload.github.com/aantron/promise/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aantron%2Fpromise/sbom","scorecard":{"id":158943,"data":{"date":"2025-08-11","repo":{"name":"github.com/aantron/promise","commit":"ffe995c431c7620362814907773fd44ce17cf972"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 2/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T12:47:23.102Z","repository_id":55960401,"created_at":"2025-08-16T12:47:23.102Z","updated_at":"2025-08-16T12:47:23.102Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29047800,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T14:55:20.264Z","status":"ssl_error","status_checked_at":"2026-02-03T14:55:19.725Z","response_time":96,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","promises","rescript"],"created_at":"2024-07-31T15:00:56.145Z","updated_at":"2026-02-03T15:04:03.353Z","avatar_url":"https://github.com/aantron.png","language":"Reason","funding_links":["https://github.com/sponsors/aantron"],"categories":["Reason"],"sub_categories":["Libraries"],"readme":"# Promise \u0026nbsp;\u0026nbsp;\u0026nbsp; [![NPM link][npm-img]][npm] [![Travis status][travis-img]][travis] [![Coverage][coveralls-img]][coveralls]\n\n[npm]: https://www.npmjs.com/package/reason-promise\n[npm-img]: https://img.shields.io/npm/v/reason-promise\n[travis]: https://travis-ci.org/aantron/promise/branches\n[travis-img]: https://img.shields.io/travis/aantron/promise/master.svg?label=travis\n[coveralls]: https://coveralls.io/github/aantron/promise?branch=master\n[coveralls-img]: https://img.shields.io/coveralls/aantron/promise/master.svg\n\nA lightweight, type-safe binding to JS promises:\n\n```rescript\nJs.log(Promise.resolved(\"Hello\"));  /* Promise { 'Hello' } */\n\nPromise.resolved(\"Hello\")\n-\u003ePromise.map(s =\u003e s ++ \" world!\")\n-\u003ePromise.get(s =\u003e Js.log(s));      /* Hello world! */\n```\n\nAs you can see on the first line, `Promise.t` maps directly to familiar JS\npromises from your JS runtime. That means...\n\n- You can use `reason-promise` directly to [write JS bindings](#Bindings).\n- All JS tooling for promises immediately works with `reason-promise`.\n- Even if you do something exotic, like switch out the promise implementation at\n  the JS level, for, say, better stack traces, `reason-promise` still binds to\n  it!\n\n\u003cbr/\u003e\n\nThere is only one exception to the rule that `Promise.t` maps directly to JS\npromises: when there is a promise nested inside another promise. JS [breaks the\ntype safety](#JSPromiseFlattening) of promises in a misguided attempt to\ndisallow nesting. [`reason-promise` instead emulates it in a way that makes\npromises type-safe again](#TypeSafety). This is in contrast to BuckleScript's\nbuilt-in `Js.Promise`, which directly exposes the JS behavior, and so is not\ntype-safe.\n\n\u003cbr/\u003e\n\nIn addition:\n\n- `reason-promise` offers a clean functional API, which replaces rejection with\n  [helpers for `Result` and `Option`](#Errors).\n- `reason-promise` is tiny. It weighs in at about [1K bundled][bundle-size].\n- `reason-promise` also has a full, standalone [pure-OCaml\n  implementation][native], which passes all the same tests. It can be used for\n  native code or in JS.\n\n[bundle-size]: https://travis-ci.org/github/aantron/promise/jobs/700562910#L210\n[native]: https://github.com/aantron/promise/tree/master/src/native\n\n\u003cbr\u003e\n\n## Tutorial\n\n- [**Installing**](#Installing)\n- [**Getting started**](#GettingStarted)\n- [**Creating new promises**](#Creating)\n- [**Getting values from promises**](#Values)\n- [**Transforming promises**](#Transforming)\n- [**Tracing**](#Tracing)\n- [**Concurrent combinations**](#Combining)\n- [**Handling errors with `Result`**](#Errors)\n- [**Advanced: Rejection**](#Rejection)\n- [**Advanced: Bindings**](#Bindings)\n- [**Discussion: Why JS promises are unsafe**](#JSPromiseFlattening)\n- [**Discussion: How `reason-promise` makes promises type-safe**](#TypeSafety)\n\n\u003cbr/\u003e\n\n\u003ca id=\"Installing\"\u003e\u003c/a\u003e\n### Installing\n\n```\nnpm install reason-promise\n```\n\nThen, add `reason-promise` to your `bsconfig.json`:\n\n```json\n{\n  \"bs-dependencies\": [\n    \"reason-promise\"\n  ]\n}\n```\n\n\u003cbr/\u003e\n\n\u003ca id=\"GettingStarted\"\u003e\u003c/a\u003e\n### Getting started\n\nTo quickly get a project for pasting the code examples, clone the\n[example repo][example-repo]. The code is in `main.re`.\n\n```\ngit clone https://github.com/aantron/promise-example-bsb\ncd promise-example-bsb\nnpm install\nnpm run test    # To run each example.\n```\n\nThere it also an example repo with\n[a trivial binding to parts of node-fetch][example-binding].\n\nWhile reading the tutorial, it can be useful to glance at the [type\nsignatures][rei] of the functions from time to time. They provide a neat summary\nof what each function does and what it expects from its callback.\n\n\u003cbr/\u003e\n\n\u003ca id=\"Creating\"\u003e\u003c/a\u003e\n### Creating new promises\n\nThe most basic function for creating a new promise is\n[`Promise.pending`][pending]:\n\n```rescript\nlet (p, resolve) = Promise.pending()\nJs.log(p)     /* Promise { \u003cpending\u003e } */\n```\n\nThe second value returned, `resolve`, is a function for resolving the promise:\n\n```rescript\nlet (p, resolve) = Promise.pending()\nresolve(\"Hello\")\nJs.log(p)     /* Promise { 'Hello' } */\n```\n\n[`Promise.resolved`][resolved] is a helper that returns an already-resolved\npromise:\n\n```rescript\nlet p = Promise.resolved(\"Hello\")\nJs.log(p)     /* Promise { 'Hello' } */\n```\n\n...and [`Promise.exec`][exec] is for wrapping functions that take callbacks:\n\n```rescript\n@bs.val external setTimeout: (unit =\u003e unit, int) =\u003e unit = \"setTimeout\"\n\nlet p = Promise.exec(resolve =\u003e setTimeout(resolve, 1000))\nJs.log(p)     /* Promise { \u003cpending\u003e } */\n\n/* Program then waits for one second before exiting. */\n```\n\n\u003cbr/\u003e\n\n\u003ca id=\"Values\"\u003e\u003c/a\u003e\n### Getting values from promises\n\nTo do something once a promise is resolved, use [`Promise.get`][get]:\n\n```rescript\nlet (p, resolve) = Promise.pending()\n\np-\u003ePromise.get(s =\u003e Js.log(s))\n\nresolve(\"Hello\")    /* Prints \"Hello\". */\n```\n\n\u003cbr/\u003e\n\n\u003ca id=\"Transforming\"\u003e\u003c/a\u003e\n### Transforming promises\n\nUse [`Promise.map`][map] to transform the value inside a promise:\n\n```rescript\nlet (p, resolve) = Promise.pending()\n\np\n-\u003ePromise.map(s =\u003e s ++ \" world\")\n-\u003ePromise.get(s =\u003e Js.log(s))\n\nresolve(\"Hello\")    /* Hello world */\n```\n\nTo be precise, `Promise.map` creates a *new* promise with the transformed value.\n\nIf the function you are using to transform the value also returns a promise,\nuse [`Promise.flatMap`][flatMap] instead of `Promise.map`. `Promise.flatMap`\nwill flatten the nested promise.\n\n\u003cbr/\u003e\n\n\u003ca id=\"Tracing\"\u003e\u003c/a\u003e\n### Tracing\n\nIf you have a chain of promise operations, and you'd like to inspect the value\nin the middle of the chain, use [`Promise.tap`][tap]:\n\n```rescript\nlet (p, resolve) = Promise.pending()\n\np\n-\u003ePromise.tap(s =\u003e Js.log(\"Value is now: \" ++ s))\n-\u003ePromise.map(s =\u003e s ++ \" world\")\n-\u003ePromise.tap(s =\u003e Js.log(\"Value is now: \" ++ s))\n-\u003ePromise.get(s =\u003e Js.log(s))\n\nresolve(\"Hello\")\n\n/*\nValue is now: Hello\nValue is now: Hello world\nHello world\n*/\n```\n\n\u003cbr/\u003e\n\n\u003ca id=\"Combining\"\u003e\u003c/a\u003e\n### Concurrent combinations\n\n[`Promise.race`][race] waits for *one* of the promises passed to it to resolve:\n\n```rescript\n@bs.val external setTimeout: (unit =\u003e unit, int) =\u003e unit = \"setTimeout\"\n\nlet one_second = Promise.exec(resolve =\u003e setTimeout(resolve, 1000))\nlet five_seconds = Promise.exec(resolve =\u003e setTimeout(resolve, 5000))\n\nPromise.race([one_second, five_seconds])\n-\u003ePromise.get(() =\u003e {\n  Js.log(\"Hello\")\n  exit(0)\n})\n\n/* Prints \"Hello\" after one second. */\n```\n\n[`Promise.all`][all] instead waits for *all* of the promises passed to it,\nconcurrently:\n\n```rescript\n@bs.val external setTimeout: (unit =\u003e unit, int) =\u003e unit = \"setTimeout\"\n\nlet one_second = Promise.exec(resolve =\u003e setTimeout(resolve, 1000))\nlet five_seconds = Promise.exec(resolve =\u003e setTimeout(resolve, 5000))\n\nPromise.all([one_second, five_seconds])\n-\u003ePromise.get(_ =\u003e {\n  Js.log(\"Hello\")\n  exit(0)\n})\n\n/* Prints \"Hello\" after five seconds. */\n```\n\nFor convenience, there are several variants of `Promise.all`:\n\n- [`Promise.all2`][all2]\n- [`Promise.all3`][all3]\n- [`Promise.all4`][all4]\n- [`Promise.all5`][all5]\n- [`Promise.all6`][all6]\n- [`Promise.allArray`][allArray]\n\n\u003cbr/\u003e\n\n\u003ca id=\"Errors\"\u003e\u003c/a\u003e\n### Handling errors with `Result`\n\nPromises that can fail are represented using the standard library's\n[`Result`][Result], and its constructors `Ok` and `Error`:\n\n```rescript\nopen Belt.Result\n\nPromise.resolved(Ok(\"Hello\"))\n-\u003ePromise.getOk(s =\u003e Js.log(s))       /* Hello */\n```\n\n[`Promise.getOk`][getOk] waits for `p` to have a value, and runs its function\nonly if that value is `Ok(_)`. If you instead resolve the promise with\n`Error(_)`, there will be no output:\n\n```rescript\nopen Belt.Result\n\nPromise.resolved(Error(\"Failed\"))\n-\u003ePromise.getOk(s =\u003e Js.log(s))       /* Program just exits. */\n```\n\nYou can wait for either kind of value by calling [`Promise.getOk`][getOk] and\n[`Promise.getError`][getError]:\n\n```rescript\nopen Belt.Result\n\nlet () = {\n  let p = Promise.resolved(Error(\"Failed\"))\n  p-\u003ePromise.getOk(s =\u003e Js.log(s))\n  p-\u003ePromise.getError(s =\u003e Js.log(\"Error: \" ++ s))\n}                                     /* Error: Failed */\n```\n\n...or respond to all outcomes using the ordinary [`Promise.get`][get]:\n\n```rescript\nopen Belt.Result\n\nPromise.resolved(Error(\"Failed\"))\n-\u003ePromise.get(result =\u003e\n  switch result {\n  | Ok(s) =\u003e Js.log(s)\n  | Error(s) =\u003e Js.log(\"Error: \" ++ s)\n  })                                  /* Error: Failed */\n```\n\nThe full set of functions for handling results is:\n\n- [`Promise.getOk`][getOk]\n- [`Promise.tapOk`][tapOk]\n- [`Promise.mapOk`][mapOk]\n- [`Promise.flatMapOk`][flatMapOk]\n- [`Promise.getError`][getError]\n- [`Promise.tapError`][tapError]\n- [`Promise.mapError`][mapError]\n- [`Promise.flatMapError`][flatMapError]\n\nThere are also similar functions for working with [`Option`][Option]:\n\n- [`Promise.getSome`][getSome]\n- [`Promise.tapSome`][tapSome]\n- [`Promise.mapSome`][mapSome]\n- [`Promise.flatMapSome`][flatMapSome]\n\nIn addition, there is also a set of variants of `Promise.all` for results, which\npropagate any `Error(_)` as soon as it is received:\n\n- [`Promise.allOk`][allOk]\n- [`Promise.allOk2`][allOk2]\n- [`Promise.allOk3`][allOk3]\n- [`Promise.allOk4`][allOk4]\n- [`Promise.allOk5`][allOk5]\n- [`Promise.allOk6`][allOk6]\n- [`Promise.allOkArray`][allOkArray]\n\nIf you'd like instead to fully wait for all the promises to resolve with either\n`Ok(_)` or `Error(_)`, you can use the ordinary `Promise.all` and its variants.\n\n\u003cbr/\u003e\n\n\u003ca id=\"Rejection\"\u003e\u003c/a\u003e\n### Advanced: Rejection\n\nAs you can see from [Handling errors](#Errors), `Promise` doesn't use rejection\nfor errors \u0026mdash; but JavaScript promises do. In order to support bindings to\nJavaScript libraries, which often return promises that can be rejected,\n`Promise` provides the [`Promise.Js`][Promise.Js] helper module.\n\n`Promise.Js` works the same way as `Promise`. It similarly has:\n\n- [`Promise.Js.get`][Js.get]\n- [`Promise.Js.tap`][Js.tap]\n- [`Promise.Js.map`][Js.map]\n- [`Promise.Js.flatMap`][Js.flatMap]\n\nHowever, because `Promise.Js` uses JS rejection for error handling rather than\n`Result` or `Option`,\n\n- There are no helpers for `Result` and `Option`.\n- There is [`Promise.Js.catch`][Js.catch] for handling rejection.\n- There is [`Promise.Js.rejected`][Js.rejected] for creating an\n  already-rejected promise.\n\nUnderneath, `Promise` and `Promise.Js` have the same implementation:\n\n```rescript\ntype Promise.t('a) = Promise.Js.t('a, never)\n```\n\nThat is, `Promise` is really `Promise.Js` that has no rejection type, and no\nexposed helpers for rejection.\n\nThere are several helpers for converting between `Promise` and `Promise.Js`:\n\n- [`Promise.Js.relax`][Js.relax]\n- [`Promise.Js.toResult`][Js.toResult]\n- [`Promise.Js.fromResult`][Js.fromResult]\n\n[`Promise.Js.catch`][Js.catch] can also perform a conversion to `Promise`, if\nyou simply convert a rejection to a resolution. In the next example, note the\nfinal line is no longer using `Promise.Js`, but `Promise`:\n\n```rescript\nPromise.Js.rejected(\"Failed\")\n-\u003ePromise.Js.catch(s =\u003e Promise.resolved(\"Error: \" ++ s))\n-\u003ePromise.get(s =\u003e Js.log(s))         /* Error: Failed */\n```\n\nThere are also two functions for converting between `Promise.Js` and the current\npromise binding in the BuckleScript standard libarary, `Js.Promise`:\n\n- [`Promise.Js.fromBsPromise`][Js.fromBsPromise]\n- [`Promise.Js.toBsPromise`][Js.toBsPromise]\n\nBecause both libraries are bindings for the same exact kind of value, these are\nboth no-op identity functions that only change the type.\n\n\u003cbr\u003e\n\n\u003ca id=\"Bindings\"\u003e\u003c/a\u003e\n### Advanced: Bindings\n\nRefer to the [example node-fetch binding repo][example-binding].\n\nWhen you want to bind a JS function that *returns* a promise, you can use\n`Promise` directly in its return value:\n\n```rescript\n/* A mock JS library. */\n%%bs.raw(`\nfunction delay(value, milliseconds) {\n  return new Promise(function(resolve) {\n    setTimeout(function() { resolve(value); }, milliseconds)\n  });\n}`)\n\n/* Our binding. */\n@bs.val external delay: ('a, int) =\u003e Promise.t('a) = \"delay\"\n\n/* Usage. */\ndelay(\"Hello\", 1000)\n-\u003ePromise.get(s =\u003e Js.log(s))\n\n/* Prints \"Hello\" after one second. */\n```\n\nIf the promise can be rejected, you should use `Promise.Js` instead, and\n[convert to `Promise`](#Rejection) as quickly as possible, with intelligent\nhandling of rejection. Here is one way to do that:\n\n```rescript\n/* Mock JS library. */\n%%bs.raw(`\nfunction delayReject(value, milliseconds) {\n  return new Promise(function(resolve, reject) {\n    setTimeout(function() { reject(value); }, milliseconds)\n  });\n}`)\n\n/* Binding. */\n@bs.val external delayRejectRaw: ('a, int) =\u003e Promise.Js.t(_, 'a) = \"delayReject\"\nlet delayReject = (value, milliseconds) =\u003e\n  delayRejectRaw(value, milliseconds)\n  -\u003ePromise.Js.toResult\n\n/* Usage. */\ndelayReject(\"Hello\", 1000)\n-\u003ePromise.getError(s =\u003e Js.log(s))\n\n/* Prints \"Hello\" after one second. */\n```\n\nNote that this binding has two steps: there is a raw binding, and then an extra\nwrapper that converts rejections into `Result`s. If the potential rejections\nare messy, this is a good place to insert additional logic for converting them\nto nice ReScript values :)\n\nWhen *passing* a promise to JS, it is generally safe to use `Promise` rather\nthan `Promise.Js`:\n\n```rescript\n/* Mock JS library. */\n%%bs.raw(`\nfunction log(p) {\n  p.then(function (v) { console.log(v); });\n}`)\n\n/* Binding. */\n@bs.val external log: Promise.t('a) =\u003e unit = \"log\"\n\n/* Usage. */\nlog(Promise.resolved(\"Hello\"))        /* Hello */\n```\n\n\u003cbr/\u003e\n\n\u003ca id=\"JSPromiseFlattening\"\u003e\u003c/a\u003e\n### Discussion: Why JS promises are unsafe\n\nThe JS function [`Promise.resolve`][Promise.resolve] has a special case, which\nis triggered when you try to resolve a promise with another, nested promise.\nUnfortunately, this special case makes it impossible to assign\n`Promise.resolve` a consistent type in ReScript (and most type systems).\n\nHere are the details. The code will use\n[`Js.Promise.resolve`][Js.Promise.resolve], BuckleScript's direct binding to\nJS's `Promise.resolve`.\n\n`Js.Promise.resolve` takes a value, and creates a promise containing that value:\n\n```rescript\nJs.log(Js.Promise.resolve(1))\n/* Promise { 1 } */\n\nJs.log(Js.Promise.resolve(\"foo\"))\n/* Promise { 'foo' } */\n```\n\nSo, we should give it the type\n\n```rescript\nJs.Promise.resolve: 'a =\u003e Js.Promise.t('a)\n```\n\nand, indeed, that's the type it [has][Js.Promise.resolve] in BuckleScript.\n\nFollowing the pattern, we would *expect*:\n\n```rescript\nlet nestedPromise = Js.Promise.resolve(1)\n\nJs.log(Js.Promise.resolve(nestedPromise))\n/* Promise { Promise { 1 } } */\n```\n\nBut that's not what happens! Instead, the output is just\n\n```rescript\n/* Promise { 1 } */\n```\n\nThe nested promise is missing! But the type system, following the pattern,\nstill thinks that this resulting value has type\n\n```rescript\nJs.Promise.t(Js.Promise.t(int))\n```\n\ni.e., the type of the value we were (reasonably) expecting.\n\nWhen you pass `nestedPromise` to `Js.Promise.resolve`, JS unwraps\n`nestedPromise`, violating the type! There is no easy way to encode such special\ncasing in the type system \u0026mdash; especially since JS does it not only to\nnested promises, but to any would-be nested object that has a `.then` method.\n\nThe result is, if your program executes something like this, it will have\nordinary values in places where it expects another level of promises. For\nexample, if you do\n\n```rescript\nlet nestedPromise = Js.Promise.resolve(1);\n\nJs.Promise.resolve(nestedPromise)\n-\u003eJs.Promise.then_(p =\u003e /* ... */)\n```\n\nyou would *expect* `p` in the callback to be a promise containing `1`, and the\ntype of `p` is indeed `Js.Promise.t(int)`. Instead, however, `p` is just the\nbare value `1`. That means the callback will cause a runtime error as soon as\nit tries to use promise functions on the `1`. Worse, you might store `p` in a\ndata structure, and the runtime error will occur at a very distant place in the\ncode. The type system is supposed to prevent such errors! That's part of the\npoint of using ReScript.\n\nThe same special casing occurs throughout the JS `Promise` API \u0026mdash; for\nexample, when you return a promise from the callback of `then_`. This means that\n*most* of the JS `Promise` functions can't be assigned a correct type and\ndirectly, safely be used from ReScript.\n\n\u003cbr/\u003e\n\n\u003ca id=\"TypeSafety\"\u003e\u003c/a\u003e\n### Discussion: How `reason-promise` makes promises type-safe\n\nThe [previous section](#JSPromiseFlattening) shows that JS promise functions are\nbroken. An important observation is that it is only the *functions* that are\nbroken \u0026mdash; the promise *data structure* is not. That means that to make JS\npromises type-safe, we can keep the existing JS data structure, and just provide\nsafe replacement functions to use with it in ReScript. This is good news\nfor interop :)\n\nTo fix the functions, only the [special-case flattening](#JSPromiseFlattening)\nhas to be undone. So, when you call `reason-promise`'s\n[`Promise.resolved(value)`][resolved], it checks whether `value` is a promise\nor not, and...\n\n- If `value` *is not* a promise, `reason-promise` just passes it to JS's\n  [`Promise.resolve`][Promise.resolve], because JS will do the right thing.\n- If `value` *is* a promise, it's not safe to simply pass it to JS, because it\n  will trigger the special-casing. So, `reason-promise` boxes the nested\n  promise:\n\n  ```rescript\n  let nestedPromise = Promise.resolved(1)\n\n  Js.log(Promise.resolved(nestedPromise))\n  /* Promise { PromiseBox { Promise { 1 } } } */\n  ```\n\n  This box, of course, is not a promise, so inserting it in the middle is\n  enough to suppress the special-casing.\n\n  Whenever you try to take the value out of this resulting structure (for\n  example, by calling [`Promise.get`][get] on it), `reason-promise`\n  transparently unboxes the `PromiseBox` and passes the nested promise to your\n  callback \u0026mdash; as your callback would expect.\n\nThis conditional boxing and unboxing is done throughout `reason-promise`. It\nonly happens for nested promises, and anything else with a `.then` method. For\nall other values, `reason-promise` behaves, internally, exactly like JS\n`Promise` (though with a cleaner outer API). This is enough to make promises\ntype-safe.\n\nThis is a simple scheme, but `reason-promise` includes a very thorough\n[test suite][tests] to be extra sure that it always manages the boxing\ncorrectly.\n\nThis conditional boxing is similar to how unboxed optionals are implemented in\nBuckleScript. Optionals are almost always unboxed, but when BuckleScript isn't\nsure that the unboxing will be safe, it inserts a runtime check that boxes some\nvalues, while still keeping most values unboxed.\n\n[example-repo]: https://github.com/aantron/promise-example-bsb\n[example-binding]: https://github.com/aantron/promise-example-binding\n[rei]: https://github.com/aantron/promise/blob/c68b1feefdd5efc0397ba92f392d6cc47233f161/src/js/promise.rei#L15\n[Result]: https://bucklescript.github.io/bucklescript/api/Belt.Result.html\n[Option]: https://bucklescript.github.io/bucklescript/api/Belt.Option.html\n[tests]: https://github.com/aantron/promise/tree/master/test\n\n[pending]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L20-L22\n[resolved]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L24-L26\n[exec]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L28-L30\n[get]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L35-L37\n[map]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L43-L45\n[flatMap]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L47-L49\n[tap]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L39-L41\n[race]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L123-L125\n[all]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L127-L129\n[all2]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L135-L137\n[all3]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L139-L141\n[all4]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L143-L145\n[all5]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L147-L149\n[all6]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L151-L158\n[allArray]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L131-L133\n[getOk]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L57-L59\n[getError]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L73-L75\n[tapOk]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L61-L63\n[tapError]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L77-L79\n[mapOk]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L65-L67\n[mapError]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L81-L83\n[flatMapOk]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L69-L71\n[flatMapError]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L85-L87\n[getSome]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L104-L106\n[tapSome]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L108-L110\n[mapSome]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L112-L114\n[flatMapSome]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L116-L118\n[allOk]: https://github.com/aantron/promise/blob/8142b0c4cb5e88e0241c3a6926fdf096b1b96935/src/js/promise.rei#L160-L162\n[allOk2]: https://github.com/aantron/promise/blob/8142b0c4cb5e88e0241c3a6926fdf096b1b96935/src/js/promise.rei#L168-L170\n[allOk3]: https://github.com/aantron/promise/blob/8142b0c4cb5e88e0241c3a6926fdf096b1b96935/src/js/promise.rei#L172-L176\n[allOk4]: https://github.com/aantron/promise/blob/8142b0c4cb5e88e0241c3a6926fdf096b1b96935/src/js/promise.rei#L178-L183\n[allOk5]: https://github.com/aantron/promise/blob/8142b0c4cb5e88e0241c3a6926fdf096b1b96935/src/js/promise.rei#L185-L191\n[allOk6]: https://github.com/aantron/promise/blob/8142b0c4cb5e88e0241c3a6926fdf096b1b96935/src/js/promise.rei#L193-L200\n[allOkArray]: https://github.com/aantron/promise/blob/8142b0c4cb5e88e0241c3a6926fdf096b1b96935/src/js/promise.rei#L164-L166\n[Promise.Js]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L163\n[Js.get]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L180-L182\n[Js.tap]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L184-L186\n[Js.map]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L188-L190\n[Js.flatMap]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L192-L194\n[Js.catch]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L197-L199\n[Js.rejected]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L175-L177\n[Js.relax]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L211-L213\n[Js.toResult]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L215-L217\n[Js.fromResult]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L219-L221\n[Js.fromBsPromise]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L223-L225\n[Js.toBsPromise]: https://github.com/aantron/promise/blob/51001f911ff31ecf51a633fba9f782769a2726c9/src/js/promise.rei#L227-L229\n[Promise.resolve]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve\n[Js.Promise.Resolve]: https://bucklescript.github.io/bucklescript/api/Js.Promise.html#VALresolve\n\n\u003c!-- YOU HAVE FOUND THE SECRET EASTER EGG! --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faantron%2Fpromise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faantron%2Fpromise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faantron%2Fpromise/lists"}