diff --git a/src/Future.re b/src/Future.re index 31f55c8..30703b0 100644 --- a/src/Future.re +++ b/src/Future.re @@ -1,47 +1,96 @@ -type getFn('a) = ('a => unit) => unit; +type getFn('a) = ('a => unit, exn => unit) => unit; type t('a) = Future(getFn('a)); let make = (resolver) => { - let callbacks = ref([]); + open Belt; + + let successCallbacks = ref([]); + let failureCallbacks = ref([]); let data = ref(None); - resolver(result => switch(data^) { + try ( + resolver( + result => switch(data^) { + | None => + data := Some(Result.Ok(result)); + successCallbacks^ |. List.reverse |. List.forEach(cb => cb(result)); + /* Clean up memory usage */ + successCallbacks := []; + failureCallbacks := [] + | Some(_) => + () /* Do nothing; theoretically not possible */ + }, + error => switch (data^) { + | None => + data := Some(Result.Error(error)); + failureCallbacks^ |. List.reverse |. List.forEach(cb => cb(error)); + + successCallbacks := []; + failureCallbacks := [] + | Some(_) => + () + } + ) + ) { + | error => data := Some(Error(error)) + }; + + Future((resolve, reject) => switch(data^) { + | Some(Ok(result)) => resolve(result) + | Some(Error(error)) => reject(error) | None => - data := Some(result); - callbacks^ |. Belt.List.reverse |. Belt.List.forEach(cb => cb(result)); - /* Clean up memory usage */ - callbacks := [] - | Some(_) => - () /* Do nothing; theoretically not possible */ - }); - - Future(resolve => switch(data^) { - | Some(result) => resolve(result) - | None => callbacks := [resolve, ...callbacks^] + successCallbacks := [resolve, ...successCallbacks^]; + failureCallbacks := [reject, ...failureCallbacks^]; }) }; -let value = (x) => make(resolve => resolve(x)); +let value = (x) => make((resolve, _reject) => resolve(x)); +let error = (e) => make((_resolve, reject) => reject(e)); -let map = (Future(get), f) => make(resolve => { - get(result => resolve(f(result))) + +let map = (Future(get), f) => make((resolve, reject) => { + get( + result => switch(f(result)) { + | value => resolve(value) + | exception error => reject(error) + }, + reject + ) }); -let flatMap = (Future(get), f) => make(resolve => { - get(result => { - let Future(get2) = f(result); - get2(resolve) - }) +let flatMap = (Future(get), f) => make((resolve, reject) => { + get( + result => switch(f(result)) { + | Future(get2) => get2(resolve, reject) + | exception error => reject(error) + }, + reject + ) }); -let tap = (Future(get) as future, f) => { - get(f); - future -}; +let tap = (Future(get), f) => make((resolve, reject) => { + get( + result => switch (f(result)) { + | () => resolve(result) + | exception error => reject(error) + }, + reject + ) +}); + +let catch = (Future(get), f) => make((resolve, reject) => { + get( + resolve, + error => switch (f(error)) { + | Future(get2) => get2(resolve, reject) + | exception error => reject(error) + } + ) +}); -let get = (Future(getFn), f) => getFn(f); +let get = (Future(getFn), resolve, reject) => getFn(resolve, reject); /* * * Future Belt.Result convenience functions, diff --git a/tests/TestFuture.re b/tests/TestFuture.re index 77ff591..d07f2d6 100644 --- a/tests/TestFuture.re +++ b/tests/TestFuture.re @@ -4,28 +4,77 @@ exception TestError(string); type timeoutId; [@bs.val] [@bs.val] external setTimeout : ([@bs.uncurry] (unit => unit), int) => timeoutId = ""; +exception Err(string); + describe("Future", () => { - let delay = (ms, f) => Future.make(resolve => + let delay = (ms, f) => Future.make((resolve, _reject) => setTimeout(() => f() |> resolve, ms) |> ignore ); + let delayError = (ms, f) => Future.make((_resolve, reject) => + setTimeout(() => f() |> reject, ms) |> ignore + ); + test("sync chaining", () => { Future.value("one") |. Future.map(s => s ++ "!") - |. Future.map(s => { - s |. equals("one!"); - }); + |. Future.get( + s => { + s |. equals("one!"); + }, + _ => { + 1 |. equals(2); + } + ) + }); + + test("sync error chaining", () => { + Future.error(Err("one")) + |. Future.catch(e => switch (e) { + | Err(s) => Err(s ++ "!") |. Future.error + | _ => assert false + }) + |. Future.get( + _ => { + 1 |. equals(2); + }, + err => { + err |. deepEquals(Err("one!")); + } + ) }); testAsync("async chaining", done_ => { delay(25, () => 20) |. Future.map(s => string_of_int(s)) |. Future.map(s => s ++ "!") - |. Future.get(s => { - s |. equals("20!"); - done_(); - }); + |. Future.get( + s => { + s |. equals("20!"); + done_(); + }, + _ => { + 1 |. equals(2); + } + ); + }); + + testAsync("async error chaining", done_ => { + delayError(25, () => Err("20")) + |. Future.catch(err => switch (err) { + | Err(s) => Err(s ++ "!") |. Future.error + | _ => assert false + }) + |. Future.get( + _ => { + 1 |. equals(2); + }, + error => { + error |. deepEquals(Err("20!")); + done_(); + } + ); }); test("tap", () => { @@ -34,36 +83,125 @@ describe("Future", () => { Future.value(99) |. Future.tap(n => v := n+1) |. Future.map(n => n - 9) - |. Future.get(n => { - n |. equals(90); - v^ |. equals(100); - }); + |. Future.get( + n => { + n |. equals(90); + v^ |. equals(100); + }, + _ => { + 1 |. equals(2); + } + ); }); test("flatMap", () => { Future.value(59) |. Future.flatMap(n => Future.value(n + 1)) - |. Future.get(n => { - n |. equals(60); - }); + |. Future.get( + n => { + n |. equals(60); + }, + _ => { + 1 |. equals(2); + } + ); + }); + + test("raising resolver", () => { + Future.make((_resolve, _reject) => raise(Err("one"))) + |. Future.get( + _ => { + 1 |. equals(2); + }, + e => { + e |. deepEquals(Err("one")); + } + ) + }); + + testAsync("raising map", done_ => { + delay(20, () => 59) + |. Future.map(_ => raise(Err("one"))) + |. Future.get( + _ => { + 1 |. equals(2); + }, + e => { + e |. deepEquals(Err("one")); + done_(); + } + ) + }); + + testAsync("raising flatMap", done_ => { + delay(20, () => 59) + |. Future.flatMap(_ => raise(Err("one"))) + |. Future.get( + _ => { + 1 |. equals(2); + }, + e => { + e |. deepEquals(Err("one")); + done_(); + } + ) + }); + + testAsync("raising tap", done_ => { + delay(20, () => 59) + |. Future.tap(_ => raise(Err("one"))) + |. Future.get( + _ => { + 1 |. equals(2); + }, + e => { + e |. deepEquals(Err("one")); + done_(); + } + ) + }); + + testAsync("raising catch", done_ => { + delay(20, () => 59) + |. Future.tap(_ => assert false) + |. Future.catch(_ => raise(Err("one"))) + |. Future.get( + _ => { + 1 |. equals(2); + }, + e => { + e |. deepEquals(Err("one")); + done_(); + } + ) }); test("multiple gets", () => { let count = ref(0); - let future = Future.make(resolve => { + let future = Future.make((resolve, _reject) => { count := count^ + 1; resolve(count^); }); count^ |. equals(1); - future |. Future.get(c => { - c |. equals(1); - }); + future |. Future.get( + c => { + c |. equals(1); + }, + _ => { + 1 |. equals(2); + } + ); count^ |. equals(1); - future |. Future.get(c => { - c |. equals(1); - }); + future |. Future.get( + c => { + c |. equals(1); + }, + _ => { + 1 |. equals(2); + } + ); count^ |. equals(1); }); @@ -76,17 +214,27 @@ describe("Future", () => { count^ |. equals(~m="Callback is async", 0); - future |. Future.get(_ => { - count^ |. equals(~m="Runs after previous future", 1); - }); + future |. Future.get( + _ => { + count^ |. equals(~m="Runs after previous future", 1); + }, + _ => { + 1 |. equals(2); + } + ); count^ |. equals(~m="Callback is async (2)", 0); - future |. Future.get(_ => { - count^ |. equals(~m="Previous future only runs once", 1); - }); + future |. Future.get( + _ => { + count^ |. equals(~m="Previous future only runs once", 1); + }, + _ => { + 1 |. equals(2); + } + ); count^ |. equals(0, ~m="Callback is async (3)"); - future |. Future.get(_ => done_()); + future |. Future.get(_ => done_(), _ => ()); }); }); @@ -98,34 +246,54 @@ describe("Future Belt.Result", () => { Belt.Result.Ok("two") |. Future.value |. Future.mapOk(s => s ++ "!") - |. Future.get(r => { - Belt.Result.getExn(r) |. equals("two!"); - }); + |. Future.get( + r => { + Belt.Result.getExn(r) |. equals("two!"); + }, + _ => { + 1 |. equals(2); + } + ); Belt.Result.Error("err2") |. Future.value |. Future.mapOk(s => s ++ "!") - |. Future.get(r => switch (r) { - | Ok(_) => raise(TestError("shouldn't be possible")) - | Error(e) => e |. equals("err2"); - }); + |. Future.get( + r => switch (r) { + | Ok(_) => raise(TestError("shouldn't be possible")) + | Error(e) => e |. equals("err2"); + }, + _ => { + 1 |. equals(2); + } + ); }); test("mapError", () => { Belt.Result.Ok("three") |. Future.value |. Future.mapError(s => s ++ "!") - |. Future.get(r => { - Belt.Result.getExn(r) |. equals("three"); - }); + |. Future.get( + r => { + Belt.Result.getExn(r) |. equals("three"); + }, + _ => { + 1 |. equals(2); + } + ); Belt.Result.Error("err3") |. Future.value |. Future.mapError(s => s ++ "!") - |. Future.get(r => switch (r) { - | Ok(_) => raise(TestError("shouldn't be possible")) - | Error(e) => e |. equals("err3!"); - }); + |. Future.get( + r => switch (r) { + | Ok(_) => raise(TestError("shouldn't be possible")) + | Error(e) => e |. equals("err3!"); + }, + _ => { + 1 |. equals(2); + } + ); }); test("tapOk", () => { @@ -135,16 +303,26 @@ describe("Future Belt.Result", () => { Belt.Result.Ok(10) |. Future.value |. Future.tapOk(n => x := x^ + n) - |. Future.get(_ => { - x^ |. equals(11); - }); + |. Future.get( + _ => { + x^ |. equals(11); + }, + _ => { + 1 |. equals(2); + } + ); Belt.Result.Error(10) |. Future.value |. Future.tapOk(n => y := y^ + n) - |. Future.get(_ => { - y^ |. equals(1); - }); + |. Future.get( + _ => { + y^ |. equals(1); + }, + _ => { + 1 |. equals(2); + } + ); }); test("tapError", () => { @@ -154,16 +332,26 @@ describe("Future Belt.Result", () => { Belt.Result.Ok(10) |. Future.value |. Future.tapError(n => x := x^ + n) - |. Future.get(_ => { - x^ |. equals(1); - }); + |. Future.get( + _ => { + x^ |. equals(1); + }, + _ => { + 1 |. equals(2); + } + ); Belt.Result.Error(10) |. Future.value |. Future.tapError(n => y := y^ + n) - |. Future.get(_ => { - y^ |. equals(11); - }); + |. Future.get( + _ => { + y^ |. equals(11); + }, + _ => { + 1 |. equals(2); + } + ); }); });