///|
/// Promise[T] wrapper for JS Promise
// #external
// pub type Promise[T]
///|
/// MoonBit builtin %async.suspend
pub async fn[T, E : Error] suspend(
f : ((T) -> Unit, (E) -> Unit) -> Unit,
) -> T raise E = "%async.suspend"
///|
/// MoonBit builtin %async.run
pub fn run_async(f : async () -> Unit noraise) -> Unit = "%async.run"
///| Types
///|
extern "js" fn ffi_promise_resolve(value : Any) -> Any =
#| (value) => Promise.resolve(value)
///|
extern "js" fn ffi_promise_reject(error : Any) -> Any =
#| (error) => Promise.reject(error)
#|
///|
extern "js" fn ffi_promise_then(promise : Any, callback : (Any) -> Any) =
#| (promise, callback) => promise.then(callback)
///|
extern "js" fn ffi_promise_all(promises : Array[Promise[Any]]) -> Promise[Any] =
#| (promises) => Promise.all(promises)
///|
extern "js" fn ffi_promise_race(promises : Array[Promise[Any]]) -> Promise[Any] =
#| (promises) => Promise.race(promises)
///|
extern "js" fn ffi_promise_any(promises : Array[Promise[Any]]) -> Promise[Any] =
#| (promises) => Promise.any(promises)
///|
extern "js" fn ffi_new_promise(
executor : ((Any) -> Unit, (Any) -> Unit) -> Unit,
) -> Promise[Any] =
#| (executor) => new Promise(executor)
///|
extern "js" fn ffi_promise_with_resolvers() -> Any =
#| () => Promise.withResolvers()
///|
/// JavaScript Promise[T]
/// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise
#external
pub type Promise[T]
///|
pub fn[T] Promise::to_any(self : Promise[T]) -> Any = "%identity"
///|
/// JS: new Promise((resolve, reject) => { ... })
/// Create a Promise from an async executor function with resolve/reject callbacks.
/// ```moonbit no-check
/// let p : Promise[Int] = Promise::new(async fn(resolve, _reject) {
/// let result = some_async_operation()
/// resolve(result)
/// })
/// ```
pub fn[A] Promise::new(
f : async ((A) -> Unit, (Error) -> Unit) -> Unit,
) -> Promise[A] {
ffi_new_promise(fn(resolve, reject) {
run_async(async fn() noraise {
f(a => a |> identity |> resolve, e => e |> identity |> reject) catch {
e => e |> identity |> reject
}
})
})
|> identity
}
///|
/// Convert an async function to a Promise.
/// ```moonbit no-check
/// let p : Promise[Int] = from_async(async fn() {
/// let result = some_async_operation()
/// result
/// })
/// ```
pub fn[A] from_async(f : async () -> A) -> Promise[A] noraise {
let { promise, resolve, reject } = Promise::withResolvers()
run_async(async fn() noraise {
try f() |> resolve catch {
e => reject(e)
}
})
promise
}
///|
/// js: Promise.resolve(value)
/// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
pub fn[A] Promise::resolve(x : A) -> Self[A] {
ffi_promise_resolve(identity(x)) |> identity
}
///|
/// js: Promise.reject(error)
/// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
pub fn Promise::reject(x : Any) -> Self[Any] {
ffi_promise_reject(identity(x)) |> identity
}
///|
pub fn[A, B] Promise::then(
self : Self[A],
resolve : (A) -> Self[B] raise,
) -> Promise[B] {
ffi_promise_then(self.to_any() |> identity, a => {
(a |> identity |> resolve |> identity) catch {
e => e |> identity |> ffi_promise_reject |> identity
}
})
|> identity
}
///|
pub fn[A, B] Promise::catch_(
self : Self[A],
f : (Error) -> Promise[B],
) -> Promise[B] {
self.to_any()._call("catch", [any(from_fn1(f))]).cast()
}
///|
/// JS: promise.finally(() => { ... })
pub fn[A] Promise::finally_(self : Self[A], f : () -> Unit) -> Self[A] {
self.to_any()._call("finally", [any(from_fn0(f))]).cast()
}
///|
/// JS: await promise
/// Waits for the Promise to be resolved or rejected.
pub async fn[T] Promise::wait(self : Self[T]) -> T {
suspend((ok, err) => {
self
.to_any()
._call("then", [any(from_fn1(ok))])
._call("catch", [any(from_fn1(err))])
|> ignore
})
}
///|
/// JS: Promise.all(promises)
/// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
/// ```moonbit no-check
/// let results = Promise::all([
/// from_async(async fn() { fetch_user() }),
/// from_async(async fn() { fetch_posts() }),
/// ]).wait()
/// ```
pub fn[A] Promise::all(promises : Array[Promise[A]]) -> Promise[Array[A]] {
ffi_promise_all(promises |> identity) |> identity
}
///|
/// JS: Promise.race(promises)
/// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
/// ```moonbit no-check
/// let first = Promise::race([
/// from_async(async fn() {
/// sleep(100)
/// "slow"
/// }),
/// from_async(async fn() {
/// sleep(10)
/// "fast"
/// }),
/// ]).wait() // "fast"
/// ```
pub fn[T] Promise::race(promises : Array[Promise[T]]) -> Promise[T] {
ffi_promise_race(promises |> identity) |> identity
}
///|
/// JS: Promise.any(promises)
/// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
/// ```moonbit no-check
/// let first_success = Promise::any([
/// from_async(async fn() { fetch_from_mirror1() }),
/// from_async(async fn() { fetch_from_mirror2() }),
/// ]).wait()
/// ```
pub fn[T] Promise::any(promises : Array[Promise[T]]) -> Promise[T] {
ffi_promise_any(promises |> identity) |> identity
}
///|
/// Result type for Promise.allSettled
pub(all) struct SettledResult[T] {
status : String // "fulfilled" | "rejected"
value : T?
reason : Nullable[Any]
}
///|
pub fn[T] SettledResult::to_any(self : SettledResult[T]) -> Any = "%identity"
///|
/// JS: Promise.allSettled(promises)
/// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
/// ```moonbit no-check
/// let results = Promise::allSettled([
/// from_async(async fn() { 1 }),
/// from_async(async fn() { fail("error") }),
/// ]).wait()
/// // results[0].status == "fulfilled", results[0].value == Some(1)
/// // results[1].status == "rejected", results[1].reason != null
/// ```
pub fn[A] Promise::allSettled(
promises : Array[Promise[A]],
) -> Promise[Array[SettledResult[A]]] {
ffi_promise_all(promises |> identity) |> identity
}
///|
/// JS: Promise.withResolvers()
/// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/withPromiseResolvers
pub(all) struct PromiseResolvers[T] {
promise : Promise[T]
resolve : (T) -> Unit
reject : (Error) -> Unit
}
///|
/// JS: Promise.withResolvers()
/// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/withPromiseResolvers
pub fn[T] Promise::withResolvers() -> PromiseResolvers[T] {
ffi_promise_with_resolvers() |> identity
}
///|
/// JS: resolvers.resolve(value)
pub fn[T] PromiseResolvers::resolve(self : Self[T], value : T) -> Unit {
(self.resolve)(value)
}
///|
/// JS: resolvers.reject(error)
pub fn[T] PromiseResolvers::reject(self : Self[T], error : Error) -> Unit {
(self.reject)(error)
}
///|
pub fn[R] promisify0(f : async () -> R) -> () -> Promise[R] noraise {
() => {
let { promise, resolve, reject } = Promise::withResolvers()
run_async(async fn() noraise {
try f() |> resolve catch {
e => reject(e)
}
})
promise
}
}
///|
/// Moonbit Async Function to JS Promise Function
pub fn[A, R] promisify1(f : async (A) -> R) -> (A) -> Promise[R] noraise {
a => {
let { promise, resolve, reject } = Promise::withResolvers()
run_async(async fn() noraise {
try f(a) |> resolve catch {
e => reject(e)
}
})
promise
}
}
///|
/// Moonbit Async Function to JS Promise Function
pub fn[A, B, R] promisify2(
f : async (A, B) -> R,
) -> (A, B) -> Promise[R] noraise {
(a, b) => {
let { promise, resolve, reject } = Promise::withResolvers()
run_async(async fn() noraise {
try f(a, b) |> resolve catch {
e => reject(e)
}
})
promise
}
}
///|
/// Moonbit Async Function to JS Promise Function
pub fn[A, B, C, R] promisify3(
f : async (A, B, C) -> R,
) -> (A, B, C) -> Promise[R] noraise {
(a, b, c) => {
let { promise, resolve, reject } = Promise::withResolvers()
run_async(async fn() noraise {
try f(a, b, c) |> resolve catch {
e => reject(e)
}
})
promise
}
}
///|
/// Sleep for given milliseconds
extern "js" fn ffi_sleep(ms : Int) -> Promise[Unit] =
#| (ms) => new Promise((resolve) => setTimeout(resolve, ms))
///|
pub async fn sleep(ms : Int) -> Unit noraise {
ffi_sleep(ms).wait() catch {
_ => panic()
}
}
///|
/// Call async dispose (Symbol.asyncDispose)
/// Usage: defer value |> dispose_async
pub async fn dispose_async(value : Any) -> Unit {
let dispose_fn : Any = value._get(symbol_async_dispose())
let promise : Promise[Unit] = Any::_invoke(dispose_fn, []) |> identity
promise.wait()
}
///|
/// Call asyncDispose but ignore the result
/// now moonbit can not async in defer
/// Usage: defer browser |> dispose_async_ignore
pub fn dispose_async_ignore(value : Any) -> Unit {
let dispose_fn : Any = value._get(symbol_async_dispose())
Any::_invoke(dispose_fn, []) |> ignore
}
///|
pub fn dispose(value : Any) -> Unit {
value._call(symbol_dispose(), []) |> ignore
}
///|
extern "js" fn symbol_dispose() -> String =
#| () => Symbol.dispose
///|
extern "js" fn symbol_async_dispose() -> String =
#| () => Symbol.asyncDispose