An implementation of `Promise` similar to Promise/A+.
## Motivation
I've found that being able to yield anywhere causes lots of bugs. In [Rodux](https://github.com/Roblox/Rodux), I explicitly made it impossible to yield in a change handler because of the sheer number of bugs that occured when callbacks randomly yielded.
As such, I think that Roblox needs an object-based async primitive. It's not important to me whether these are Promises, Observables, Task objects, or Futures.
The important traits are:
* An object that represents a unit of asynchronous work
* Composability
* Predictable timing
This Promise implementation attempts to satisfy those traits.
* Set a handler that will be called regardless of the promise's fate. The handler is called when the promise is resolved, rejected, *or* cancelled.
* Returns a new promise chained from this promise.
*`Promise:cancel() -> nil`
* Cancels this promise, preventing the promise from resolving or rejecting. Does not do anything if the promise is already settled.
* Cancellations will propagate upwards through chained promises.
* Promises will only be cancelled if all of their consumers are also cancelled. This is to say that if you call `andThen` twice on the same promise, and you cancel only one of the promises resulting from the `andThen` call, it will not cancel the parent promise until the other child promise is also cancelled.
* Yields the current thread until the given Promise completes. Returns `ok` as a bool, followed by the value that the promise returned.
## Example
This Promise implementation finished synchronously. In order to wrap an existing async API, you should use `spawn` or `delay` in order to prevent your calling thread from accidentally yielding.
```lua
local HttpService = game:GetService("HttpService")
-- A light wrapper around HttpService
-- Ideally, you do this once per project per async method that you use.
local function httpGet(url)
return Promise.new(function(resolve, reject)
-- Spawn to prevent yielding, since GetAsync yields.
spawn(function()
local ok, result = pcall(HttpService.GetAsync, HttpService, url)
if ok then
resolve(result)
else
reject(result)
end
end)
end)
end
-- Usage
httpGet("https://google.com")
:andThen(function(body)
print("Here's the Google homepage:", body)
end)
:catch(function(err)
warn("We failed to get the Google homepage!", err)
end)
```
## Future Additions
*`Promise.wrapAsync`
* Intended to wrap an existing Roblox API that yields, exposing a new one that returns a Promise.