Lib: add Promise + Fetch bindings (#2031, #596)#2239
Open
hhugo wants to merge 7 commits into
Open
Conversation
This was referenced May 12, 2026
vouillon
requested changes
May 20, 2026
Bind JavaScript promises with an indirection wrapper that prevents the
runtime from auto-flattening thenables, so 'a Promise.t Promise.t stays
sound at the OCaml type level.
The wrap/unwrap indirection lives in runtime/{js,wasm}/promise.{js,wat}
and is exposed via Jsoo_runtime.Promise. Wrap is conditional on the value
being thenable, so non-thenable resolves pay no allocation, and foreign
promises handed in via [of_any] flow through [then_] unchanged.
[then_] accepts an [?on_error] for the two-callback .then(f, g) form,
distinct from [then_ |> catch] in that [on_error] only fires for
rejections of the input promise.
Js_of_ocaml_lwt.Promise extends Js_of_ocaml.Promise with [to_lwt] and [of_lwt] for converting between Lwt promises and JavaScript promises. Rejections of foreign promises are surfaced to Lwt as a Failure carrying [Js_error.to_string].
- requestFullscreen / requestPointerLock / exitFullscreen: add unit Promise.t Js.meth variants, keeping the existing fire-and-forget forms under the _-suffixed names. - mediaElement: rename existing fire-and-forget play to play_; add play : unit Promise.t meth so callers can handle autoplay rejections (NotAllowedError). - animation: add finished and ready Promise<Animation> properties.
Adds: - Abort module: general-purpose AbortController/AbortSignal primitive (also usable by event listeners, streams, custom cancellation). - Fetch module: Headers, Request, RequestInit, Response, ResponseInit class types, plus the [fetch] entry point. The body-reader methods (text, json, blob, arrayBuffer, formData) are typed with Promise.t. Uses Abort.signal for the [signal] field. Out of scope (left for follow-ups): ReadableStream / response body streaming, Request.clone-from-Request constructor variant, FormData constructor improvements.
Codify the leading-underscore-for-mangling vs trailing-underscore-for- alternate-form split that the codebase has informally followed. Helps future contributors avoid adding new keyword escapes via trailing _ (which is reserved for argument overloads and legacy fire-and-forget variants of Promise-typed methods).
Three method bindings used multi-underscore names that ppx_js unescape mangles to a JS identifier different from the intended one (unescape drops everything from the last `_`): - Typed_array._BYTES_PER_ELEMENT -> BYTES_PER (intended BYTES_PER_ELEMENT) - WebGL._MAX_RENDERBUFFER_SIZE -> MAX_RENDERBUFFER (intended MAX_RENDERBUFFER_SIZE) - canvasElement.toDataURL_type_compression -> toDataURL_type (intended toDataURL; toDataURL_type is the existing 1-arg overload) Renamed to _BYTES_PER_ELEMENT_, _MAX_RENDERBUFFER_SIZE_, and toDataURL_compression respectively so the JS calls resolve correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The mangler drops the last `_` and everything after it, so names with more than one non-leading underscore resolve to something shorter than intended (this is what motivated the preceding mangling-bug fixes for `_BYTES_PER_ELEMENT`, `_MAX_RENDERBUFFER_SIZE`, and `toDataURL_type_compression`). Document the rule with a small examples table and the two escape patterns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is following #2221
Summary
Closes #2031 and addresses the long-standing Fetch request (#596).
Promisemodule inlib/js_of_ocaml/: bindings to JS Promise with an indirection wrapper that prevents auto-flattening of thenables, so'a Promise.t Promise.tstays sound at the OCaml type level. Wrap helpers live inruntime/{js,wasm}/promise.{js,wat}and are exposed viaJsoo_runtime.Promise. Wrapping is conditional on the value being thenable, so non-thenable resolves pay no allocation and foreign promises handed in viaof_anyflow through unchanged.then_takes an optional?on_errorfor the two-callback.then(f, g)form.Js_of_ocaml_lwt.Promise.{to_lwt,of_lwt}for Lwt interop, with aRejected of errorexception for non-OCaml rejection reasons.Abortmodule: general-purposeAbortController/AbortSignalprimitive, separate fromFetchso it can be reused by future bindings (Streams, custom cancellation, …).Fetchmodule:Headers,Request,RequestInit,Response,ResponseInitclass types and thefetchentry point. Body-reader methods (text,json,blob,arrayBuffer,formData) typed withPromise.t. UsesAbort.signalfor thesignalfield.Dom_htmlbindings:requestFullscreen/exitFullscreen/requestPointerLocknow returnunit Promise.t Js.meth(legacy fire-and-forget kept under_-suffixed names);mediaElement.playreturnsunit Promise.t Js.meth(legacyplay_kept);Animation.finishedandAnimation.readyexposed asanimation t Promise.t readonly_prop.CONTRIBUTING.md: documents the existing leading-_(mangling) vs trailing-_(alternate form / legacy stub) split for new bindings.API-STATUS.md: updated to mark Promise, Fetch, AbortController, Fullscreen, Pointer Lock as bound; collapsed Tier 1.Out of scope (left for follow-ups):
ReadableStream/ response body streaming,Requestclone-from-Request constructor, richerFormDataintegration, the wider modern surface onnavigator(mediaDevices, clipboard, serviceWorker, permissions, credentials, …).Test plan
dune build(jsoo)WASM_OF_OCAML=true dune build(wasm)dune runtest lib/tests— sync sanity tests forPromise(constructor / instanceof / make sync body)dune runtest compiler/tests-jsoo— end-to-end asynctest_promisecovering resolve / then_ / catch / finally / all / race / nested promise /of_anyof a foreign promise / reject-with-promise (verifies that JS doesn't auto-follow on rejection)dune runtest compiler/tests-jsoo—test_lwt_promiseround-trips Promise ↔ Lwt for both fulfilled and rejected paths🤖 Generated with Claude Code