mirror of
https://github.com/AmberGraceRblx/luau-promise.git
synced 2025-04-24 23:50:03 +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
|
CC0 1.0 Universal
|
||||||
distribute this software, either in source code form or as a compiled
|
|
||||||
binary, for any purpose, commercial or non-commercial, and by any
|
|
||||||
means.
|
|
||||||
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
of this software dedicate any and all copyright interest in the
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
software to the public domain. We make this dedication for the benefit
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
of the public at large and to the detriment of our heirs and
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
successors. We intend this dedication to be an overt act of
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
relinquishment in perpetuity of all present and future rights to this
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
software under copyright law.
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
|
HEREUNDER.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Statement of Purpose
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
## 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
|
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
|
-- If promise debugging is on, use a version of pcall that warns on failure.
|
||||||
-- happen within Promise itself.
|
-- This is useful for finding errors that happen within Promise itself.
|
||||||
local wpcall
|
local wpcall
|
||||||
if PROMISE_DEBUG then
|
if PROMISE_DEBUG then
|
||||||
wpcall = function(f, ...)
|
wpcall = function(f, ...)
|
||||||
|
@ -71,8 +71,8 @@ Promise.Status = {
|
||||||
end
|
end
|
||||||
|
|
||||||
get("https://google.com")
|
get("https://google.com")
|
||||||
:andThen(function(body)
|
:andThen(function(stuff)
|
||||||
print("Got a body:", body)
|
print("Got some stuff!", stuff)
|
||||||
end)
|
end)
|
||||||
]]
|
]]
|
||||||
function Promise.new(callback)
|
function Promise.new(callback)
|
||||||
|
@ -139,23 +139,8 @@ end
|
||||||
* is resolved when all input promises resolve
|
* is resolved when all input promises resolve
|
||||||
* is rejected if ANY input promises reject
|
* is rejected if ANY input promises reject
|
||||||
]]
|
]]
|
||||||
function Promise.all(promises)
|
function Promise.all(...)
|
||||||
return Promise.new(function(resolve, reject)
|
error("unimplemented", 2)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
@ -193,7 +178,7 @@ function Promise:andThen(successHandler, failureHandler)
|
||||||
end
|
end
|
||||||
|
|
||||||
if self._status == Promise.Status.Started then
|
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._queuedResolve, successCallback)
|
||||||
table.insert(self._queuedReject, failureCallback)
|
table.insert(self._queuedReject, failureCallback)
|
||||||
elseif self._status == Promise.Status.Resolved then
|
elseif self._status == Promise.Status.Resolved then
|
||||||
|
@ -323,4 +308,4 @@ function Promise:_reject(...)
|
||||||
end
|
end
|
||||||
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": {
|
"partitions": {
|
||||||
"lib": {
|
"lib": {
|
||||||
"path": "lib",
|
"path": "lib",
|
||||||
"target": "ReplicatedStorage.lua-promise"
|
"target": "ReplicatedStorage.Promise"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue