Add Return and done

Closes #9
This commit is contained in:
Eryn Lynn 2019-09-28 22:03:06 -04:00
parent 3052ec1474
commit bb13a0e2b6
4 changed files with 267 additions and 2 deletions

View file

@ -8,6 +8,8 @@
- Improve stack traces
- Promise.promisify will now turn errors into rejections even if they occur after a yield.
- Add Promise.try
- Add `done`, `doneCall`, `doneReturn`
- Add `andThenReturn`, `finallyReturn`
# 2.4.0

View file

@ -322,6 +322,31 @@ docs:
returns: Promise<T>
returns: Promise<T>
- name: done
desc: |
Set a handler that will be called only if the Promise resolves or is cancelled. This method is similar to `finally`, except it doesn't catch rejections.
::: warning
If this Promise is cancelled, any Promises chained off of it with `andThen` won't run. Only Promises chained with `done` and `finally` will run in the case of cancellation.
:::
Returns a new promise chained from this promise.
params:
- name: doneHandler
type:
kind: function
params: "status: PromiseStatus"
returns: ...any?
returns: Promise<...any?>
overloads:
- params:
- name: doneHandler
type:
kind: function
params: "status: PromiseStatus"
returns: Promise<T>
returns: Promise<T>
- name: andThenCall
desc: |
Attaches an `andThen` handler to this Promise that calls the given callback with the predefined arguments. The resolved value is discarded.
@ -364,6 +389,87 @@ docs:
desc: Arguments which will be passed to the callback.
returns: Promise
- name: doneCall
desc: |
Same as `andThenCall`, except for `done`.
Attaches a `done` handler to this Promise that calls the given callback with the predefined arguments.
params:
- name: callback
type:
kind: function
params: "...: ...any?"
returns: "any"
- name: "..."
type: "...any?"
desc: Arguments which will be passed to the callback.
returns: Promise
- name: andThenReturn
desc: |
Attaches an `andThen` handler to this Promise that discards the resolved value and returns the given value from it.
```lua
promise:andThenReturn("some", "values")
```
This is sugar for
```lua
promise:andThen(function()
return "some", "values"
end)
```
params:
- name: "..."
type: "...any?"
desc: Values to return from the function.
returns: Promise<...any?>
- name: finallyReturn
desc: |
Attaches a `finally` handler to this Promise that discards the resolved value and returns the given value from it.
```lua
promise:finallyReturn("some", "values")
```
This is sugar for
```lua
promise:finally(function()
return "some", "values"
end)
```
params:
- name: "..."
type: "...any?"
desc: Values to return from the function.
returns: Promise<...any?>
- name: doneReturn
desc: |
Attaches a `done` handler to this Promise that discards the resolved value and returns the given value from it.
```lua
promise:doneReturn("some", "values")
```
This is sugar for
```lua
promise:done(function()
return "some", "values"
end)
```
params:
- name: "..."
type: "...any?"
desc: Values to return from the function.
returns: Promise<...any?>
- name: cancel
desc: |
Cancels this promise, preventing the promise from resolving or rejecting. Does not do anything if the promise is already settled.

View file

@ -502,6 +502,16 @@ function Promise.prototype:andThenCall(callback, ...)
end)
end
--[[
Shorthand for an andThen handler that returns the given value.
]]
function Promise.prototype:andThenReturn(...)
local length, values = pack(...)
return self:_andThen(debug.traceback(), function()
return unpack(values, 1, length)
end)
end
--[[
Cancels the promise, disallowing it from rejecting or resolving, and calls
the cancellation hook if provided.
@ -548,8 +558,10 @@ end
Used to set a handler for when the promise resolves, rejects, or is
cancelled. Returns a new promise chained from this promise.
]]
function Promise.prototype:_finally(traceback, finallyHandler)
self._unhandledRejection = false
function Promise.prototype:_finally(traceback, finallyHandler, onlyOk)
if not onlyOk then
self._unhandledRejection = false
end
-- Return a promise chained off of this promise
return Promise._new(traceback, function(resolve, reject)
@ -563,6 +575,17 @@ function Promise.prototype:_finally(traceback, finallyHandler)
)
end
if onlyOk then
local callback = finallyCallback
finallyCallback = function(...)
if self._status == Promise.Status.Rejected then
return resolve(self)
end
return callback(...)
end
end
if self._status == Promise.Status.Started then
-- The promise is not settled, so queue this.
table.insert(self._queuedFinally, finallyCallback)
@ -592,6 +615,48 @@ function Promise.prototype:finallyCall(callback, ...)
end)
end
--[[
Shorthand for a finally handler that returns the given value.
]]
function Promise.prototype:finallyReturn(...)
local length, values = pack(...)
return self:_finally(debug.traceback(), function()
return unpack(values, 1, length)
end)
end
--[[
Similar to finally, except rejections are propagated through it.
]]
function Promise.prototype:done(finallyHandler)
assert(
finallyHandler == nil or type(finallyHandler) == "function",
ERROR_NON_FUNCTION:format("Promise:finallyO")
)
return self:_finally(debug.traceback(), finallyHandler, true)
end
--[[
Calls a callback on `done` with specific arguments.
]]
function Promise.prototype:doneCall(callback, ...)
assert(type(callback) == "function", ERROR_NON_FUNCTION:format("Promise:doneCall"))
local length, values = pack(...)
return self:_finally(debug.traceback(), function()
return callback(unpack(values, 1, length))
end, true)
end
--[[
Shorthand for a done handler that returns the given value.
]]
function Promise.prototype:doneReturn(...)
local length, values = pack(...)
return self:_finally(debug.traceback(), function()
return unpack(values, 1, length)
end, true)
end
--[[
Yield until the promise is completed.

View file

@ -416,6 +416,18 @@ return function()
expect(p2._parent).to.equal(p1)
expect(p1._consumers[p2]).to.equal(true)
end)
it("should forward return values", function()
local value
Promise.resolve():finally(function()
return 1
end):andThen(function(v)
value = v
end)
expect(value).to.equal(1)
end)
end)
describe("Promise.all", function()
@ -698,4 +710,84 @@ return function()
expect(errorText:find("errortext")).to.be.ok()
end)
end)
describe("Promise:andThenReturn", function()
it("should return the given values", function()
local value1, value2
Promise.resolve():andThenReturn(1, 2):andThen(function(one, two)
value1 = one
value2 = two
end)
expect(value1).to.equal(1)
expect(value2).to.equal(2)
end)
end)
describe("Promise:doneReturn", function()
it("should return the given values", function()
local value1, value2
Promise.resolve():doneReturn(1, 2):andThen(function(one, two)
value1 = one
value2 = two
end)
expect(value1).to.equal(1)
expect(value2).to.equal(2)
end)
end)
describe("Promise:andThenCall", function()
it("should call the given function with arguments", function()
local value1, value2
Promise.resolve():andThenCall(function(a, b)
value1 = a
value2 = b
end, 3, 4)
expect(value1).to.equal(3)
expect(value2).to.equal(4)
end)
end)
describe("Promise:doneCall", function()
it("should call the given function with arguments", function()
local value1, value2
Promise.resolve():doneCall(function(a, b)
value1 = a
value2 = b
end, 3, 4)
expect(value1).to.equal(3)
expect(value2).to.equal(4)
end)
end)
describe("Promise:done", function()
it("should trigger on resolve or cancel", function()
local promise = Promise.new(function() end)
local value
local p = promise:done(function()
value = true
end)
expect(value).to.never.be.ok()
promise:cancel()
expect(p:getStatus()).to.equal(Promise.Status.Cancelled)
expect(value).to.equal(true)
local never, always
Promise.reject():done(function()
never = true
end):finally(function()
always = true
end)
expect(never).to.never.be.ok()
expect(always).to.be.ok()
end)
end)
end