mirror of
https://github.com/AmberGraceRblx/luau-promise.git
synced 2025-04-24 15:50:01 +00:00
Add Promise.async
This commit is contained in:
parent
c5fc5bff27
commit
264ff27213
4 changed files with 121 additions and 9 deletions
|
@ -32,6 +32,7 @@ module.exports = {
|
|||
],
|
||||
|
||||
themeConfig: {
|
||||
activeHeaderLinks: false,
|
||||
searchOptions: {
|
||||
placeholder: 'Press S to search...'
|
||||
},
|
||||
|
|
|
@ -4,22 +4,71 @@ title: Implementation Details
|
|||
|
||||
# Implementation Details
|
||||
|
||||
## Yielding in Promise executor
|
||||
## Chaining
|
||||
|
||||
If you need to yield in the Promise executor, you must wrap your yielding code in a new thread to prevent your calling thread from yielding. The easiest way to do this is to wrap your code with the built-in `Promise.spawn`:
|
||||
One of the best parts about Promises is that they are chainable.
|
||||
|
||||
Every time you call `andThen` or `catch`, it returns a *new* Promise, which resolves with whatever value you return from the success or failure handlers, respectively.
|
||||
|
||||
```lua
|
||||
Promise.new(function(resolve)
|
||||
somePromise:andThen(function(number)
|
||||
return number + 1
|
||||
end):andThen(print)
|
||||
```
|
||||
|
||||
You can also return a Promise from your handler, and it will be chained onto:
|
||||
|
||||
```lua
|
||||
Promise.async(function(resolve)
|
||||
wait(1)
|
||||
resolve(1)
|
||||
end):andThen(function(x)
|
||||
return Promise.async(function(resolve)
|
||||
wait(1)
|
||||
resolve(x + 1)
|
||||
end)
|
||||
end):andThen(print) --> 2
|
||||
```
|
||||
|
||||
You can also call `:andThen` multiple times on a single Promise to have multiple branches off of a single Promise.
|
||||
|
||||
Resolving a Promise with a Promise will be chained as well:
|
||||
```lua
|
||||
Promise.async(function(resolve)
|
||||
wait(1)
|
||||
resolve(Promise.async(function(resolve)
|
||||
wait(1)
|
||||
resolve(1)
|
||||
end))
|
||||
end):andThen(print) --> 1
|
||||
```
|
||||
|
||||
However, any value that is returned from the Promise executor (the function you pass into `Promise.async`) is discarded. Do not return values from the function executor.
|
||||
|
||||
## Yielding in Promise executor
|
||||
|
||||
If you need to yield in the Promise executor, you must wrap your yielding code in a new thread to prevent your calling thread from yielding. The easiest way to do this is to use the <ApiLink to="Promise.async" /> constructor instead of <ApiLink to="Promise.new" />:
|
||||
|
||||
```lua
|
||||
Promise.async(function(resolve)
|
||||
wait(1)
|
||||
resolve()
|
||||
end)
|
||||
```
|
||||
|
||||
`Promise.async` uses `Promise.new` internally, except it wraps the Promise executor with <ApiLink to="Promise.spawn" />.
|
||||
|
||||
`Promise.async` is sugar for:
|
||||
|
||||
```lua
|
||||
Promise.new(function(resolve, reject, onCancel)
|
||||
Promise.spawn(function()
|
||||
wait(1)
|
||||
resolve()
|
||||
-- ...
|
||||
end)
|
||||
end)
|
||||
```
|
||||
|
||||
`Promise.spawn` uses a BindableEvent internally to launch your Promise body on a fresh thread after waiting for the next `RunService.Heartbeat` event.
|
||||
|
||||
The reason `Promise.spawn` includes this wait time is to ensure that your Promises have consistent timing. Otherwise, your Promise would run synchronously up to the first yield, and asynchronously afterwards. This can often lead to undesirable results. Additionally, Promises that never yield can resolve completely synchronously, and this can lead to unpredictable timing issues. Thus, we use `Promise.spawn` so there is always a guaranteed yield before execution.
|
||||
`Promise.spawn` uses a BindableEvent internally to launch your Promise body on a fresh thread after waiting for the next `RunService.Heartbeat` event. The reason `Promise.spawn` includes this wait time is to ensure that your Promises have consistent timing. Otherwise, your Promise would run synchronously up to the first yield, and asynchronously afterwards. This can often lead to undesirable results. Additionally, Promises that never yield can resolve completely synchronously, and this can lead to unpredictable timing issues. Thus, we use `Promise.spawn` so there is always a guaranteed yield before execution.
|
||||
|
||||
::: danger Don't use regular spawn
|
||||
`spawn` might seem like a tempting alternative to `Promise.spawn` here, but you should **never** use it!
|
||||
|
@ -31,6 +80,11 @@ The reason `Promise.spawn` includes this wait time is to ensure that your Promis
|
|||
`coroutine.wrap` is another possible stand-in for creating a BindableEvent and firing it off, but in the case of an error, the stack trace is reset when the coroutine executes. This can make troubleshooting extremely difficult because you don't know where to look in your code base for the source of the error. Creating a BindableEvent is relatively cheap, so you shouldn't need to worry about this causing performance problems in your game.
|
||||
:::
|
||||
|
||||
### When to use `Promise.new`
|
||||
In some cases, it is desirable for a Promise to execute completely synchronously. If you don't need to yield in your Promise executor, and you are aware of the timing implications of a completely synchronous Promise, then it is acceptable to use `Promise.new`.
|
||||
|
||||
However, in these situations, <ApiLink to="Promise.resolve" /> may be more appropriate.
|
||||
|
||||
## Cancellation details
|
||||
If a Promise is already cancelled at the time of calling its onCancel hook, the hook will be called immediately.
|
||||
|
||||
|
|
|
@ -26,8 +26,15 @@ docs:
|
|||
|
||||
functions:
|
||||
- name: new
|
||||
tags: [ 'constructor' ]
|
||||
desc: |
|
||||
Construct a new Promise that will be resolved or rejected with the given callbacks.
|
||||
|
||||
::: tip
|
||||
Generally, it is recommended to use [[Promise.async]] instead. You cannot directly yield inside the `executor` function of [[Promise.new]].
|
||||
:::
|
||||
|
||||
If you `resolve` with a Promise, it will be chained onto.
|
||||
|
||||
You may register an optional cancellation hook by using the `onCancel` argument.
|
||||
* This should be used to abort any ongoing operations leading up to the promise being settled.
|
||||
|
@ -60,6 +67,44 @@ docs:
|
|||
- name: abortHandler
|
||||
kind: function
|
||||
returns: void
|
||||
returns: Promise
|
||||
- name: async
|
||||
tags: [ 'constructor' ]
|
||||
desc: |
|
||||
The same as [[Promise.new]], except it implicitly uses `Promise.spawn` internally. Use this if you want to yield inside your Promise body.
|
||||
|
||||
::: tip
|
||||
Promises created with [[Promise.async]] are guaranteed to yield for at least one frame, even if the executor function doesn't yield itself. <a href="/roblox-lua-promise/lib/Details.html#yielding-in-promise-executor">Learn more</a>
|
||||
:::
|
||||
static: true
|
||||
params:
|
||||
- name: asyncExecutor
|
||||
type:
|
||||
kind: function
|
||||
params:
|
||||
- name: resolve
|
||||
type:
|
||||
kind: function
|
||||
params:
|
||||
- name: "..."
|
||||
type: ...any?
|
||||
returns: void
|
||||
- name: reject
|
||||
type:
|
||||
kind: function
|
||||
params:
|
||||
- name: "..."
|
||||
type: ...any?
|
||||
returns: void
|
||||
- name: onCancel
|
||||
type:
|
||||
kind: function
|
||||
params:
|
||||
- name: abortHandler
|
||||
kind: function
|
||||
returns: void
|
||||
returns: Promise
|
||||
|
||||
- name: resolve
|
||||
desc: Creates an immediately resolved Promise with the given value.
|
||||
static: true
|
||||
|
@ -106,7 +151,10 @@ docs:
|
|||
type: "...any?"
|
||||
|
||||
- name: andThen
|
||||
desc: Chains onto an existing Promise and returns a new Promise.
|
||||
desc: |
|
||||
Chains onto an existing Promise and returns a new Promise.
|
||||
|
||||
Return a Promise from the success or failure handler and it will be chained onto.
|
||||
params:
|
||||
- name: successHandler
|
||||
type:
|
||||
|
|
|
@ -189,6 +189,15 @@ function Promise.new(callback, parent)
|
|||
return self
|
||||
end
|
||||
|
||||
--[[
|
||||
Promise.new, except Promise.spawn is implicit.
|
||||
]]
|
||||
function Promise.async(callback)
|
||||
return Promise.new(function(...)
|
||||
return Promise.spawn(callback, ...)
|
||||
end)
|
||||
end
|
||||
|
||||
--[[
|
||||
Spawns a thread with predictable timing.
|
||||
]]
|
||||
|
|
Loading…
Reference in a new issue