Skip to main content

interop-promise

ETS-JavaScript interop Promise bridging system in ArkCompiler. Use this skill when working on cross-language Promise conversion between ETS (ArkTS) and JavaScript, including JSConvertPromise Wrap/Unwrap, EtsPromise proxy creation, EtsPromiseRef bridging, CreatePromiseLink, OnJsPromiseCompleted callbacks, connectPromise, SettleJsPromise, PromiseInteropResolve/Reject, EtsAwaitPromise/AwaitProxyPromise, callback queue management, or any code under js_convert.h (Promise section), js_job_queue, ets_promise, ets_promise_ref, std_core_Promise.cpp, or PromiseInterop.ets. Also use when debugging cross-VM Promise state synchronization, coroutine suspension/resumption during await, or napi_deferred lifecycle issues.

Source facts

Repository
openharmonyinsight/openharmony-skills
Last source activity
June 16, 2026 at 01:42
Detected SKILL.md language
English
Stars
34
Forks
7

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
4 files

Showing SKILL.md

SKILL.md
Source instructions ยท Read-only preview
name
interop-promise
description
ETS-JavaScript interop Promise bridging system in ArkCompiler. Use this skill when working on cross-language Promise conversion between ETS (ArkTS) and JavaScript, including JSConvertPromise Wrap/Unwrap, EtsPromise proxy creation, EtsPromiseRef bridging, CreatePromiseLink, OnJsPromiseCompleted callbacks, connectPromise, SettleJsPromise, PromiseInteropResolve/Reject, EtsAwaitPromise/AwaitProxyPromise, callback queue management, or any code under js_convert.h (Promise section), js_job_queue, ets_promise, ets_promise_ref, std_core_Promise.cpp, or PromiseInterop.ets. Also use when debugging cross-VM Promise state synchronization, coroutine suspension/resumption during await, or napi_deferred lifecycle issues.
# Interop Promise - ETS/JS Cross-Language Promise Bridging Guide for understanding, developing, and debugging the Promise interop system that bridges ETS (ArkTS) Promises with JavaScript Promises in the ArkCompiler hybrid runtime. ## Architecture Overview The interop Promise system enables transparent bidirectional Promise conversion between the static ETS VM and dynamic JS VM. When ETS calls a JS async function (or vice versa), the system creates proxy objects and registers callbacks so that resolve/reject events propagate correctly across the language boundary. ``` ETS VM Side JS VM Side โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ EtsPromise JS Promise โ”‚ โ”‚ โ”œโ”€โ”€ interopObject_ โ†’ EtsPromiseRef โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€ linkedPromise_ โ†’ EtsPromiseRef โ”‚ โ”œโ”€โ”€ event_ (await suspend/resume) โ”‚ โ”œโ”€โ”€ mutex_ (thread safety) โ”‚ โ””โ”€โ”€ callbackQueue_ (.then handlers) โ”‚ โ”‚ SharedReferenceStorage โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ (EtsPromiseRef โ†” JS Promise mapping) JSโ†’ETS path: JS Promise โ†’ JSCONVERT_UNWRAP(Promise) โ†’ EtsPromise proxy ETSโ†’JS path: EtsPromise โ†’ JSCONVERT_WRAP(Promise) โ†’ JS Promise ``` ## Source Code Locations | Component | Path | |-----------|------| | JSConvertPromise (Wrap/Unwrap) | `plugins/ets/runtime/interop_js/js_convert.h` | | EtsPromise class | `plugins/ets/runtime/types/ets_promise.h/.cpp` | | EtsPromiseRef bridge | `plugins/ets/runtime/types/ets_promise_ref.h` | | JsJobQueue / CreatePromiseLink | `plugins/ets/runtime/interop_js/js_job_queue.h/.cpp` | | Promise intrinsics | `plugins/ets/runtime/intrinsics/std_core_Promise.cpp` | | SettleJsPromise / PromiseInterop | `plugins/ets/runtime/interop_js/intrinsics_api_impl.cpp` | | PromiseInterop.ets | `plugins/ets/stdlib/std/interop/js/PromiseInterop.ets` | | ETS Promise.ets | `plugins/ets/stdlib/std/core/Promise.ets` | | CallJSHandler (ETSโ†’JS calls) | `plugins/ets/runtime/interop_js/call/call_js.cpp` | | CallETSHandler (JSโ†’ETS calls) | `plugins/ets/runtime/interop_js/call/call_ets.cpp` | | Type routing (ConvertArgToEts/JS) | `plugins/ets/runtime/interop_js/call/arg_convertors.h` | ## Two Core Conversion Paths ### Path 1: JS Promise โ†’ ETS Proxy (JSCONVERT_UNWRAP) Triggered when ETS calls a JS function that returns a Promise. **Call chain:** ``` CallJSHandler::Handle() โ†’ ConvertRetval() โ†’ ConvertArgToEts() โ†’ ConvertRefArgToEts() โ†’ JSConvertPromise::UnwrapImpl() [JSCONVERT_UNWRAP(Promise)] ``` **UnwrapImpl steps:** 1. **Identity check**: `SharedReferenceStorage::GetReference(env, jsVal)` โ€” reuse existing proxy if JS Promise was already wrapped 2. **Create proxy EtsPromise**: `EtsPromise::Create(coro)` โ€” STATE_PENDING 3. **Create EtsPromiseRef bridge**: Why? Because SharedReferenceStorage uses MarkWord for interop hash, and EtsPromise uses MarkWord for Lock โ€” they conflict 4. **Register mapping**: `SharedReferenceStorage::CreateJSObjectRef(ctx, ref, jsVal)` โ€” bidirectional EtsPromiseRef โ†” JS Promise 5. **Mark as proxy**: `hpromise->SetLinkedPromise(coro, href)` โ€” enables `IsProxy()` check in await 6. **Create link**: `EtsPromise::CreateLink()` โ†’ `JsJobQueue::CreatePromiseLink()` โ€” registers C++ callbacks on JS Promise's `.then()` ### Path 2: ETS Promise โ†’ JS Promise (JSCONVERT_WRAP) Triggered when JS calls an ETS function that returns a Promise. **Call chain:** ``` CallETSHandler::HandleImpl() โ†’ ConvertArgToJS() โ†’ ConvertRefArgToJS() โ†’ JSConvertPromise::WrapImpl() [JSCONVERT_WRAP(Promise)] ``` **WrapImpl has two paths:** **Fast path** (EtsPromise already settled when Wrap is called): 1. Identity check via `GetInteropObject()` + `HasReference()` 2. `napi_create_promise(env, &deferred, &jsPromise)` โ€” create pending JS Promise 3. `Lock()` โ†’ check `!IsPending() && !IsLinked()` โ†’ fast path 4. Convert ETS value to JS value via `JSRefConvertResolve` 5. `napi_resolve_deferred()` or `napi_reject_deferred()` โ€” immediately settle JS Promise 6. Create EtsPromiseRef + register in SharedReferenceStorage **Slow path** (EtsPromise still pending): 1-2. Same as fast path 3. `Lock()` โ†’ `IsPending() || IsLinked()` โ†’ slow path 4. `Unlock()` โ†’ call `PromiseInterop.connectPromise(promise, deferred)` via Invoke 5. `connectPromise` registers `.then()` callbacks that call `PromiseInteropResolve/Reject` native methods 6. When EtsPromise resolves later: `OnPromiseCompletion()` โ†’ `LaunchCallback()` โ†’ `PromiseInteropResolve()` โ†’ `SettleJsPromise()` โ†’ `napi_resolve_deferred()` 7. Create EtsPromiseRef + register in SharedReferenceStorage ## Key Classes ### EtsPromise (`ets_promise.h`) States: `STATE_PENDING(0)`, `STATE_RESOLVED(1)`, `STATE_REJECTED(2)`, `STATE_LINKED(3)` | Method | Description | |--------|-------------| | `Create(coro)` | Create PENDING promise with mutex + event | | `Resolve(coro, value)` | Set value, transition to RESOLVED, call OnPromiseCompletion | | `Reject(coro, error)` | Set error, transition to REJECTED, call OnPromiseCompletion | | `Wait()` | Block coroutine via `EtsEvent::Wait()` | | `IsProxy()` | `linkedPromise_ != nullptr` โ€” true for JS Promise proxies | | `IsPending/Resolved/Rejected/Linked()` | State checkers | | `SubmitCallback(cb, workerDomain)` | Add .then handler to callbackQueue | | `CreateLink(source, target)` | Delegate to `JobQueue::CreateLink()` | | `OnPromiseCompletion(coro)` | Fire event, launch queued callbacks, handle unhandled rejection | | `LaunchCallback(coro, cb, groupId)` | Execute callback in new coroutine (PROMISE_CALLBACK priority) | | `ChangeStateToPendingFromLinked()` | LINKED โ†’ PENDING state transition | | `GetInteropObject()` / `SetInteropObject()` | EtsPromiseRef bridge object | | `GetLinkedPromise()` / `SetLinkedPromise()` | For proxy detection | | `Lock()` / `Unlock()` / `IsLocked()` | Thread-safe mutex via MarkWord | **Member variables:** `value_`, `mutex_`, `event_`, `callbackQueue_`, `workerDomainQueue_`, `interopObject_`, `linkedPromise_`, `queueSize_`, `state_` ### EtsPromiseRef (`ets_promise_ref.h`) Minimal bridge object to avoid MarkWord conflict between SharedReferenceStorage (interop hash) and EtsPromise (Lock). ```cpp class EtsPromiseRef : public EtsObject { EtsObject *target_ {}; // Points to the actual EtsPromise // MarkWord used by SharedReferenceStorage for interop hash index }; ``` ### JsJobQueue (`js_job_queue.h/.cpp`) Extends `JobQueue` with JS-specific callback and promise linking. | Method | Description | |--------|-------------| | `CreatePromiseLink(jsObject, etsPromise)` | Register C++ then/catch callbacks on JS Promise | | `Post(fn, data)` | Post callback to JS job queue via JS Promise | **Global C++ callbacks registered on JS Promise:** - `OnJsPromiseResolved(env, info)` โ†’ delegates to `OnJsPromiseCompleted(env, info, true)` - `OnJsPromiseRejected(env, info)` โ†’ delegates to `OnJsPromiseCompleted(env, info, false)` - `OnJsPromiseCompleted(env, info, isResolved)`: Converts JS value to ETS, calls `EtsPromiseResolve` or `EtsPromiseReject` ## ETS Await Mechanism ``` ETS: await p; โ†’ EtsAwaitPromise(p) โ†’ IsProxy()? โ†’ YES: AwaitProxyPromise() โ†’ promise->Wait() // EtsEvent::Wait() โ€” coroutine suspends โ†’ [JS resolves โ†’ OnJsPromiseCompleted โ†’ EtsPromiseResolve โ†’ Resolve โ†’ OnPromiseCompletion โ†’ Fire()] โ†’ Wait() returns โ†’ IsResolved()? return GetValue() : throw exception โ†’ NO: promise->Wait() // Direct ETS Promise await ``` **Key**: `EtsAwaitPromise` first yields CPU via `coro->GetManager()->Schedule()` to allow other coroutines (including JS microtasks) to execute before checking proxy status. ## Promise State Machine ``` Create() โ”‚ โ–ผ STATE_PENDING / โ”‚ \ resolve() / CreateLink() \ reject() / โ”‚ \ โ–ผ โ–ผ โ–ผ STATE_RESOLVED STATE_LINKED STATE_REJECTED โ”‚ resolve() โ”‚ (from subscribeOnAnotherPromise) โ–ผ STATE_RESOLVED ``` - **PENDING**: Initial state, or after ChangeStateToPendingFromLinked() - **LINKED**: Proxy Promise waiting for JS source to settle - **RESOLVED/REJECTED**: Terminal states, value_ holds result ## Callback Queue and Execution `.then()` registration flow: ``` p.then(onResolve, onReject) โ†’ Promise.ets: thenImpl() โ†’ [native] EtsPromiseSubmitCallback(promise, callback, workerDomain) โ†’ SubmitCallback(): if settled โ†’ execute immediately; else โ†’ add to callbackQueue_ ``` Execution on completion: ``` OnPromiseCompletion(coro) โ†’ Fire() // Wake awaiters โ†’ for each callback in queue: โ†’ LaunchCallback(coro, callback, groupId) โ†’ Create CompletionEvent โ†’ coroManager->Launch(event, method, args, groupId, PROMISE_CALLBACK) ``` **Queue capacity management**: Dynamic resizing with `EnsureCapacity()` โ€” growth strategy is `2 * oldSize + 1`. ## SettleJsPromise โ€” ETSโ†’JS Bridge The final step that completes a JS Promise from ETS: ```cpp void SettleJsPromise(EtsObject *value, napi_deferred deferred, EtsInt state) { // Must run on main worker thread INTEROP_CODE_SCOPE_ETS_TO_JS(executionCtx); // Convert ETS value to JS value completionValue = refconv->Wrap(ctx, value); // Complete the JS Promise napi_resolve_deferred(env, deferred, completionValue); // or napi_reject_deferred } ``` Called from `PromiseInteropResolve()` / `PromiseInteropReject()` which are native methods invoked by `PromiseInterop.ets` callbacks. ## PromiseInterop.ets โ€” Slow Path Connection ```typescript final class PromiseInterop { static connectPromise<T>(p: Promise<T>, deferred: long): void { p.then<void, void>( (value: T): void => { PromiseInterop.resolve<T>(value, deferred); }, (error: Any): void => { PromiseInterop.reject(error, deferred); } ); } private static native resolve<T>(value: T, deferred: long): void; private static native reject(error: Any, deferred: long): void; } ``` This ETS code is invoked via `PlatformTypes()->interopPromiseInteropConnectPromise->GetPandaMethod()->Invoke()` from C++ during JSCONVERT_WRAP slow path. ## Type Routing โ€” How Promise Conversion is Triggered Promise conversion is **not** triggered by runtime type detection (e.g., `napi_is_promise`). Instead, it's driven by **compile-time type signatures** from `.d.ets` files: ``` .d.ets: export declare function jsAsync(): Promise<string>; โ†“ (compiler generates ProtoReader type info) ProtoReader return type = EtsPromise class โ†“ (runtime type routing) ConvertRefArgToEts โ†’ JSRefConvertResolve(ctx, EtsPromise.RuntimeClass) โ†“ (finds JSConvertPromise converter) JSConvertPromise::UnwrapImpl() or WrapImpl() ``` If `.d.ets` declares `Promise<T>` but JS returns non-Promise, `ASSERT(isPromise)` fails in Debug mode. ## Return Value Type Routing (Fast Path Reference) | Direction | Entry Point | Router | Converter | |-----------|-------------|--------|-----------| | JSโ†’ETS (return) | `CallJSHandler::ConvertRetval()` | `ConvertArgToEts()` โ†’ `ConvertRefArgToEts()` | `JSConvertPromise::UnwrapImpl()` | | ETSโ†’JS (return) | `CallETSHandler::ConvertArgToJS()` | `ConvertRefArgToJS()` | `JSConvertPromise::WrapImpl()` | | JSโ†’ETS (param) | `CallETSHandler::ConvertArgs()` | `ConvertArgToEts()` | `JSConvertPromise::UnwrapImpl()` | | ETSโ†’JS (param) | `CallJSHandler::ConvertArgsAndCall()` | `ConvertArgToJS()` | `JSConvertPromise::WrapImpl()` | ## Testing Patterns Three verification patterns exist for interop Promise testing: | Pattern | ETS Promise State at Return | JSCONVERT_WRAP Path | JS Verification | |---------|----------------------------|---------------------|-----------------| | A: ETS internal verify | PENDING | Slow path | Poll ETS global state | | B: JS verify resolved | RESOLVED | Fast path | JS `.then()` on returned Promise | | C: JS verify pending | PENDING | Slow path | JS `.then()` + setTimeout trigger resolve | ## Threading and Coroutine Considerations - **SettleJsPromise** must execute on main worker thread (`ASSERT(IsMainWorker())`) - **Promise callbacks** launch in new coroutines with `PROMISE_CALLBACK` priority - **Worker domains**: MAIN or GENERAL โ€” affects which coroutine group handles the callback - **Coroutine switch**: `EtsAwaitPromise` yields CPU before suspending to allow JS microtask processing - **Mutex**: EtsPromise uses MarkWord-based Lock for thread safety (hence need for EtsPromiseRef) - **Event**: EtsEvent provides coroutine suspension/resumption for await ## Common Tasks ### Adding a new interop type that can cross Promise boundaries 1. Implement `JSConvert<YourType>::Wrap()` and `UnwrapImpl()` in `js_convert.h` 2. Register in `JSRefConvertResolve` lookup chain
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub