mirror of
https://github.com/AmberGraceRblx/luau-promise.git
synced 2025-04-24 15:50:01 +00:00
Update the repository for prime-time
This commit is contained in:
parent
bafeb68973
commit
9bc03fc001
6 changed files with 464 additions and 175 deletions
137
LICENSE
137
LICENSE
|
@ -1,24 +1,121 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
Creative Commons Legal Code
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
CC0 1.0 Universal
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
Statement of Purpose
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
78
README.md
78
README.md
|
@ -1,4 +1,78 @@
|
|||
# Lua Promise
|
||||
# Roblox Lua Promise
|
||||
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.
|
||||
|
||||
## API
|
||||
|
||||
### Static Functions
|
||||
* `Promise.new((resolve, reject) -> nil) -> Promise`
|
||||
* Construct a new Promise that will be resolved or rejected with the given callbacks.
|
||||
* `Promise.resolve(value) -> Promise`
|
||||
* Creates an immediately resolved Promise with the given value.
|
||||
* `Promise.reject(value) -> Promise`
|
||||
* Creates an immediately rejected Promise with the given value.
|
||||
* `Promise.is(object) -> bool`
|
||||
* Returns whether the given object is a Promise.
|
||||
|
||||
### Instance Methods
|
||||
* `Promise:andThen(successHandler, [failureHandler]) -> Promise`
|
||||
* Chains onto an existing Promise and returns a new Promise.
|
||||
* Equivalent to the Promise/A+ `then` method.
|
||||
* `Promise:catch(failureHandler) -> Promise`
|
||||
* Shorthand for `Promise:andThen(nil, failureHandler)`.
|
||||
* `Promise:await() -> ok, value`
|
||||
* 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.all`
|
||||
* Currently stubbed out, throws an error.
|
||||
* `Promise.wrapAsync`
|
||||
* Intended to wrap an existing Roblox API that yields, exposing a new one that returns a Promise.
|
||||
|
||||
## License
|
||||
Lua Promise is available under the terms of the Unlicense. See [LICENSE](LICENSE) for details.
|
||||
This project is available under the CC0 license. See [LICENSE](LICENSE) for details.
|
|
@ -1,129 +0,0 @@
|
|||
return function()
|
||||
local Promise = require(script.Parent.Promise)
|
||||
|
||||
describe("new", function()
|
||||
it("should pass resolve and reject to the callback", function()
|
||||
local promiseResolve
|
||||
local promiseReject
|
||||
local callCount = 0
|
||||
|
||||
local promise = Promise.new(function(resolve, reject)
|
||||
callCount = callCount + 1
|
||||
promiseResolve = resolve
|
||||
promiseReject = reject
|
||||
end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promiseResolve).to.be.a("function")
|
||||
expect(promiseReject).to.be.a("function")
|
||||
expect(callCount).to.equal(1)
|
||||
end)
|
||||
|
||||
it("should resolve synchronously", function()
|
||||
local promiseResolve
|
||||
local callCount = 0
|
||||
|
||||
local promise = Promise.new(function(resolve)
|
||||
callCount = callCount + 1
|
||||
promiseResolve = resolve
|
||||
end)
|
||||
|
||||
expect(promise._status).to.equal(Promise.Status.Started)
|
||||
|
||||
promiseResolve(6, 7)
|
||||
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(promise._value).to.be.a("table")
|
||||
expect(#promise._value).to.equal(2)
|
||||
expect(promise._value[1]).to.equal(6)
|
||||
expect(promise._value[2]).to.equal(7)
|
||||
end)
|
||||
|
||||
it("should reject synchronously", function()
|
||||
local promiseReject
|
||||
local callCount = 0
|
||||
|
||||
local promise = Promise.new(function(_, reject)
|
||||
callCount = callCount + 1
|
||||
promiseReject = reject
|
||||
end)
|
||||
|
||||
expect(promise._status).to.equal(Promise.Status.Started)
|
||||
|
||||
promiseReject(6, 7)
|
||||
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value).to.be.a("table")
|
||||
expect(#promise._value).to.equal(2)
|
||||
expect(promise._value[1]).to.equal(6)
|
||||
expect(promise._value[2]).to.equal(7)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("resolve", function()
|
||||
it("should be a synchronously resolved promise", function()
|
||||
local promise = Promise.resolve(3)
|
||||
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(promise._value).to.be.a("table")
|
||||
expect(#promise._value).to.equal(1)
|
||||
expect(promise._value[1]).to.equal(3)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("reject", function()
|
||||
it("should be a synchronously rejected promise", function()
|
||||
local promise = Promise.reject(3)
|
||||
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value).to.be.a("table")
|
||||
expect(#promise._value).to.equal(1)
|
||||
expect(promise._value[1]).to.equal(3)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("andThen", function()
|
||||
it("should be chained with unresolved promises", function()
|
||||
local rootResolve
|
||||
local rootCallCount = 0
|
||||
local childCallCount = 0
|
||||
local childValues
|
||||
|
||||
local root = Promise.new(function(resolve)
|
||||
rootCallCount = rootCallCount + 1
|
||||
rootResolve = resolve
|
||||
end)
|
||||
|
||||
local child = root:andThen(function(...)
|
||||
childCallCount = childCallCount + 1
|
||||
childValues = {...}
|
||||
|
||||
return "foo"
|
||||
end)
|
||||
|
||||
expect(root).never.to.equal(child)
|
||||
expect(rootCallCount).to.equal(1)
|
||||
expect(childCallCount).to.equal(0)
|
||||
|
||||
expect(root._status).to.equal(Promise.Status.Started)
|
||||
expect(child._status).to.equal(Promise.Status.Started)
|
||||
|
||||
rootResolve(16, 13)
|
||||
|
||||
expect(root._status).to.equal(Promise.Status.Resolved)
|
||||
expect(root._value).to.be.a("table")
|
||||
expect(#root._value).to.equal(2)
|
||||
expect(root._value[1]).to.equal(16)
|
||||
expect(root._value[2]).to.equal(13)
|
||||
|
||||
expect(#childValues).to.equal(2)
|
||||
expect(childValues[1]).to.equal(16)
|
||||
expect(childValues[2]).to.equal(13)
|
||||
|
||||
expect(child._status).to.equal(Promise.Status.Resolved)
|
||||
expect(child._value).to.be.a("table")
|
||||
expect(#child._value).to.equal(1)
|
||||
expect(child._value[1]).to.equal("foo")
|
||||
end)
|
||||
end)
|
||||
end
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
local PROMISE_DEBUG = false
|
||||
|
||||
-- If promise debugging is on, use a version of pcall that warns on failure. This is useful for finding errors that
|
||||
-- happen within Promise itself.
|
||||
-- If promise debugging is on, use a version of pcall that warns on failure.
|
||||
-- This is useful for finding errors that happen within Promise itself.
|
||||
local wpcall
|
||||
if PROMISE_DEBUG then
|
||||
wpcall = function(f, ...)
|
||||
|
@ -71,8 +71,8 @@ Promise.Status = {
|
|||
end
|
||||
|
||||
get("https://google.com")
|
||||
:andThen(function(body)
|
||||
print("Got a body:", body)
|
||||
:andThen(function(stuff)
|
||||
print("Got some stuff!", stuff)
|
||||
end)
|
||||
]]
|
||||
function Promise.new(callback)
|
||||
|
@ -139,23 +139,8 @@ end
|
|||
* is resolved when all input promises resolve
|
||||
* is rejected if ANY input promises reject
|
||||
]]
|
||||
function Promise.all(promises)
|
||||
return Promise.new(function(resolve, reject)
|
||||
local results = {}
|
||||
local totalCount = #promises
|
||||
local finishedCount = 0
|
||||
|
||||
for index, promise in ipairs(promises) do
|
||||
promise:andThen(function(value)
|
||||
results[index] = value
|
||||
finishedCount = finishedCount + 1
|
||||
|
||||
if finishedCount == totalCount then
|
||||
resolve(results)
|
||||
end
|
||||
end, reject)
|
||||
end
|
||||
end)
|
||||
function Promise.all(...)
|
||||
error("unimplemented", 2)
|
||||
end
|
||||
|
||||
--[[
|
||||
|
@ -193,7 +178,7 @@ function Promise:andThen(successHandler, failureHandler)
|
|||
end
|
||||
|
||||
if self._status == Promise.Status.Started then
|
||||
-- If we haven't resolved yet, put ourself into the queue
|
||||
-- If we haven't resolved yet, put ourselves into the queue
|
||||
table.insert(self._queuedResolve, successCallback)
|
||||
table.insert(self._queuedReject, failureCallback)
|
||||
elseif self._status == Promise.Status.Resolved then
|
||||
|
@ -323,4 +308,4 @@ function Promise:_reject(...)
|
|||
end
|
||||
end
|
||||
|
||||
return Promise
|
||||
return Promise
|
262
lib/init.spec.lua
Normal file
262
lib/init.spec.lua
Normal file
|
@ -0,0 +1,262 @@
|
|||
return function()
|
||||
local Promise = require(script.Parent)
|
||||
|
||||
describe("Promise.new", function()
|
||||
it("should instantiate with a callback", function()
|
||||
local promise = Promise.new(function() end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
end)
|
||||
|
||||
it("should invoke the given callback with resolve and reject", function()
|
||||
local callCount = 0
|
||||
local resolveArg
|
||||
local rejectArg
|
||||
|
||||
local promise = Promise.new(function(resolve, reject)
|
||||
callCount = callCount + 1
|
||||
resolveArg = resolve
|
||||
rejectArg = reject
|
||||
end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(resolveArg).to.be.a("function")
|
||||
expect(rejectArg).to.be.a("function")
|
||||
expect(promise._status).to.equal(Promise.Status.Started)
|
||||
end)
|
||||
|
||||
it("should resolve promises on resolve()", function()
|
||||
local callCount = 0
|
||||
|
||||
local promise = Promise.new(function(resolve)
|
||||
callCount = callCount + 1
|
||||
resolve()
|
||||
end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(callCount).to.equal(1)
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
end)
|
||||
|
||||
it("should reject promises on reject()", function()
|
||||
local callCount = 0
|
||||
|
||||
local promise = Promise.new(function(resolve, reject)
|
||||
callCount = callCount + 1
|
||||
reject()
|
||||
end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(callCount).to.equal(1)
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
end)
|
||||
|
||||
it("should reject on error in callback", function()
|
||||
local callCount = 0
|
||||
|
||||
local promise = Promise.new(function()
|
||||
callCount = callCount + 1
|
||||
error("hahah")
|
||||
end)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(callCount).to.equal(1)
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]:find("hahah")).to.be.ok()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Promise.resolve", function()
|
||||
it("should immediately resolve with a value", function()
|
||||
local promise = Promise.resolve(5)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(promise._value[1]).to.equal(5)
|
||||
end)
|
||||
|
||||
it("should chain onto passed promises", function()
|
||||
local promise = Promise.resolve(Promise.new(function(_, reject)
|
||||
reject(7)
|
||||
end))
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(7)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Promise.reject", function()
|
||||
it("should immediately reject with a value", function()
|
||||
local promise = Promise.reject(6)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(6)
|
||||
end)
|
||||
|
||||
it("should pass a promise as-is as an error", function()
|
||||
local innerPromise = Promise.new(function(resolve)
|
||||
resolve(6)
|
||||
end)
|
||||
|
||||
local promise = Promise.reject(innerPromise)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(innerPromise)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Promise:andThen", function()
|
||||
it("should chain onto resolved promises", function()
|
||||
local args
|
||||
local argsLength
|
||||
local callCount = 0
|
||||
local badCallCount = 0
|
||||
|
||||
local promise = Promise.resolve(5)
|
||||
|
||||
local chained = promise
|
||||
:andThen(function(...)
|
||||
args = {...}
|
||||
argsLength = select("#", ...)
|
||||
callCount = callCount + 1
|
||||
end, function()
|
||||
badCallCount = badCallCount + 1
|
||||
end)
|
||||
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(argsLength).to.equal(1)
|
||||
expect(args[1]).to.equal(5)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(promise._value[1]).to.equal(5)
|
||||
|
||||
expect(chained).to.be.ok()
|
||||
expect(chained).never.to.equal(promise)
|
||||
expect(chained._status).to.equal(Promise.Status.Resolved)
|
||||
expect(#chained._value).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should chain onto rejected promises", function()
|
||||
local args
|
||||
local argsLength
|
||||
local callCount = 0
|
||||
local badCallCount = 0
|
||||
|
||||
local promise = Promise.reject(5)
|
||||
|
||||
local chained = promise
|
||||
:andThen(function(...)
|
||||
badCallCount = badCallCount + 1
|
||||
end, function(...)
|
||||
args = {...}
|
||||
argsLength = select("#", ...)
|
||||
callCount = callCount + 1
|
||||
end)
|
||||
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(argsLength).to.equal(1)
|
||||
expect(args[1]).to.equal(5)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(5)
|
||||
|
||||
expect(chained).to.be.ok()
|
||||
expect(chained).never.to.equal(promise)
|
||||
expect(chained._status).to.equal(Promise.Status.Resolved)
|
||||
expect(#chained._value).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should chain onto asynchronously resolved promises", function()
|
||||
local args
|
||||
local argsLength
|
||||
local callCount = 0
|
||||
local badCallCount = 0
|
||||
|
||||
local startResolution
|
||||
local promise = Promise.new(function(resolve)
|
||||
startResolution = resolve
|
||||
end)
|
||||
|
||||
local chained = promise
|
||||
:andThen(function(...)
|
||||
args = {...}
|
||||
argsLength = select("#", ...)
|
||||
callCount = callCount + 1
|
||||
end, function()
|
||||
badCallCount = badCallCount + 1
|
||||
end)
|
||||
|
||||
expect(callCount).to.equal(0)
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
startResolution(6)
|
||||
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(argsLength).to.equal(1)
|
||||
expect(args[1]).to.equal(6)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Resolved)
|
||||
expect(promise._value[1]).to.equal(6)
|
||||
|
||||
expect(chained).to.be.ok()
|
||||
expect(chained).never.to.equal(promise)
|
||||
expect(chained._status).to.equal(Promise.Status.Resolved)
|
||||
expect(#chained._value).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should chain onto asynchronously rejected promises", function()
|
||||
local args
|
||||
local argsLength
|
||||
local callCount = 0
|
||||
local badCallCount = 0
|
||||
|
||||
local startResolution
|
||||
local promise = Promise.new(function(_, reject)
|
||||
startResolution = reject
|
||||
end)
|
||||
|
||||
local chained = promise
|
||||
:andThen(function()
|
||||
badCallCount = badCallCount + 1
|
||||
end, function(...)
|
||||
args = {...}
|
||||
argsLength = select("#", ...)
|
||||
callCount = callCount + 1
|
||||
end)
|
||||
|
||||
expect(callCount).to.equal(0)
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
startResolution(6)
|
||||
|
||||
expect(badCallCount).to.equal(0)
|
||||
|
||||
expect(callCount).to.equal(1)
|
||||
expect(argsLength).to.equal(1)
|
||||
expect(args[1]).to.equal(6)
|
||||
|
||||
expect(promise).to.be.ok()
|
||||
expect(promise._status).to.equal(Promise.Status.Rejected)
|
||||
expect(promise._value[1]).to.equal(6)
|
||||
|
||||
expect(chained).to.be.ok()
|
||||
expect(chained).never.to.equal(promise)
|
||||
expect(chained._status).to.equal(Promise.Status.Resolved)
|
||||
expect(#chained._value).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
end
|
|
@ -4,7 +4,7 @@
|
|||
"partitions": {
|
||||
"lib": {
|
||||
"path": "lib",
|
||||
"target": "ReplicatedStorage.lua-promise"
|
||||
"target": "ReplicatedStorage.Promise"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue