Skip to content

Commit

Permalink
Address feedback and update README
Browse files Browse the repository at this point in the history
  • Loading branch information
tom committed Dec 12, 2019
1 parent 2a5d362 commit f3031fa
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 8 deletions.
9 changes: 9 additions & 0 deletions README.md
Expand Up @@ -143,6 +143,15 @@ somePromiseGetter()

See [Composible Error Handling in OCaml][error-handling] for several strategies that you may employ.

### Stack Safety

By default this library is not stack safe, you will recieve a 'Maximum call stack size exceeded' error if you recurse too deeply. You can opt into stack safety by passing an optional parameter to the constructors of trampoline. This has a slight overhead. For example:

```reason
let stackSafe = Future.make(~executor=`trampoline, resolver);
let stackSafeValue = Future.value(~executor=`trampoline, "Value");
```

## TODO

- [ ] Implement cancellation tokens
Expand Down
30 changes: 28 additions & 2 deletions __tests__/TestFuture.re
Expand Up @@ -317,16 +317,23 @@ describe("Future Belt.Result", () => {
let numberOfLoops = 10000;
let rec loop = x => {
next(x)
->Future.flatMap(x => Future.value(x + 1))
->Future.map(x => x + 1)
->Future.flatMap(x =>
Future.value(x + 1)->Future.flatMap(x => Future.value(x + 1))
)
->Future.flatMap(x' =>
if (x' == numberOfLoops) {
if (x' > numberOfLoops) {
Future.value(x');
} else {
loop(x');
}
);
};
loop(0)
->Future.get(r => r |> expect |> toEqual(numberOfLoops) |> finish);
->Future.get(r =>
r |> expect |> toBeGreaterThan(numberOfLoops) |> finish
);
});

testAsync("async recursion is stack safe", finish => {
Expand All @@ -345,4 +352,23 @@ describe("Future Belt.Result", () => {
loop(0)
->Future.get(r => r |> expect |> toEqual(numberOfLoops) |> finish);
});

test("value recursion blows the stack with default executor", () => {
let next = x => Future.value(x + 1);
let numberOfLoops = 10000;
let rec loop = x => {
next(x)
->Future.flatMap(x' =>
if (x' > numberOfLoops) {
Future.value(x');
} else {
loop(x');
}
);
};
expect(() =>
loop(0)
)
|> toThrowMessage("Maximum call stack size exceeded");
});
});
4 changes: 3 additions & 1 deletion __tests__/TestUtil.re
Expand Up @@ -5,4 +5,6 @@ type timeoutId;
external setTimeout: ([@bs.uncurry] (unit => unit), int) => timeoutId = "";

let delay = (~executor=`none, ms, f) =>
Future.make(~executor, resolve => setTimeout(() => f() |> resolve, ms) |> ignore);
Future.make(~executor, resolve =>
setTimeout(() => f() |> resolve, ms) |> ignore
);
10 changes: 5 additions & 5 deletions src/Future.re
@@ -1,9 +1,9 @@
type getFn('a) = ('a => unit) => unit;

type executorOptions = [ | `none | `trampoline];
type executorType = [ | `none | `trampoline];

type t('a) =
| Future(getFn('a), executorOptions);
| Future(getFn('a), executorType);

let trampoline = {
let running = ref(false);
Expand All @@ -25,7 +25,7 @@ let trampoline = {
};
};

let make = (~executor: executorOptions=`none, resolver) => {
let make = (~executor: executorType=`none, resolver) => {
let callbacks = ref([]);
let data = ref(None);

Expand All @@ -51,14 +51,14 @@ let make = (~executor: executorOptions=`none, resolver) => {
Future(
resolve =>
switch (data^) {
| Some(result) => trampoline(() => resolve(result))
| Some(result) => runCallback(result, resolve)
| None => callbacks := [resolve, ...callbacks^]
},
executor,
);
};

let value = (~executor: executorOptions=`none, x) =>
let value = (~executor: executorType=`none, x) =>
make(~executor, resolve => resolve(x));

let map = (Future(get, executor), f) =>
Expand Down

0 comments on commit f3031fa

Please sign in to comment.