Skip to main content

Callbacks ((...) => T)

Callbacks are functions created in one language and passed to another to provide a way to "call back" later.

Nitro has a clever reference counting system to allow users to use callbacks/functions from JS safely, and without any limitations. Each callback holds a strong reference on the native side and can be called as often as needed. Once the callback is no longer used, it will be safely deleted from memory.

In TypeScript, a callback is represented as an anonymous function:

type Orientation = "portrait" | "landscape"
interface DeviceInfo extends HybridObject {
listenToOrientation(onChanged: (o: Orientation) => void): void
}

const deviceInfo = // ...
deviceInfo.listenToOrientation((o) => {
console.log(`Orientation changed to ${o}!`)
})

Since callbacks can be safely kept in memory for longer and called multiple times, Nitro does not have a special type for an "event". It is simply a function you store in memory and call later. ✨

Callbacks that return a value ((...) => T)

Since JS callbacks could theoretically be called from any native Thread, Nitro safely wraps the result types of callbacks that return a value in Promises which need to be awaited.

Math.nitro.ts
interface Math extends HybridObject {
some(getValue: () => number): void
}
HybridMath.swift
func some(getValue: () -> Promise<Double>) {
Task {
let promise = getValue()
let valueFromJs = promise.await()
}
}

How was it before Nitro?

Conventionally (in legacy React Native Native Modules), a native method could only have a maximum of two callbacks, one "success" and one "failure" callback. Once one of these callbacks is called, both will be destroyed and can no longer be called later. This is why React Native introduced "Events" as a way to call into JS more than just once. This also meant that an asynchronous function could not have any callbacks, since a Promise's resolve and reject functions are already two callbacks. For example, this was not possible:

interface Camera {
startRecording(onStatusUpdate: () => void,
onRecordingFailed: () => void,
onRecordingFinished: () => void): Promise<void>
}

Thanks to Nitro's clever reference system, functions can be safely held in memory and called as many times as you like, just like in a normal JS class. This makes "Events" obsolete, and allows using as many callbacks per native method as required.