khanlou / promise Goto Github PK
View Code? Open in Web Editor NEWA Promise library for Swift, based partially on Javascript's A+ spec
License: MIT License
A Promise library for Swift, based partially on Javascript's A+ spec
License: MIT License
There are 3 methods titled then
on a Promise object
public func then(on queue: ExecutionContext = DispatchQueue.main, _ onFulfilled: @escaping (Value) -> (), _ onRejected: @escaping (Error) -> () = { _ in }) -> Promise<Value>
public func then<NewValue>(on queue: ExecutionContext = DispatchQueue.main, _ onFulfilled: @escaping (Value) throws -> NewValue) -> Promise<NewValue>
public func then<NewValue>(on queue: ExecutionContext = DispatchQueue.main, _ onFulfilled: @escaping (Value) throws -> Promise<NewValue>) -> Promise<NewValue>
onFulfilled
of the first method returns Void
, of the second - NewValue
, and the third - Promise<NewValue>
, that's important.
If you were to write
Promise { fulfill, reject in
fulfill(3)
}.then { result in
return
}
then it's dandy, the Void
is returned
If you were to write
Promise { fulfill, reject in
fulfill(3)
}.then { result in
return Promise(value: result)
}
then it's also dandy, the swiftc will figure out that the return type is Promise
However
Promise { fulfill, reject in
fulfill(3)
}.then { result in
return result
}
never just works on its own; there is an issue with type inference and compiler errors with "expected type Promise<_>"
The way you do it so it gets figured out is
Promise { fulfill, reject in
fulfill(3)
}.then { result -> Int in
return result
}
Then it's alright.
Observed in XCode 9.2 GM, Swift 4.0.2
Also observed in Swift 4.0.3 environment on Linux.
It may or may not help to rename the handler from onFulfilled
to a different name such as onFulfilledWithValue
in the method where handler returns NewValue
.
Apologies if this isn't the best place to ask, but I'm having difficult understanding what's going on in this function, more specifically: why do we create a promise that's seemingly not used for anything? Thanks!
Lines 187 to 202 in f5b2821
Thanks for writing #58, that's a nice feature, thanks!
I was looking for a way to mimick a switch-like decision tree with these error types. But from the looks of the current implementation, consecutive catch blocks will all be called when the promise is rejected:
(this test just serves to illustrate all three catch blocks are executed, sequentially)
There also seems to be no error-type based recover
-method that would allow me to catch (and squelch) errors of a specific type, so if I want to be "picky" about specific error types, I still have to write the decision tree within the catch block, instead of leveraging the Promise API.
How would you approach something like this?
Cheers,
Eric-Paul
Im using this framework in an app-extension and get warning that it is not safe for use in application extension.
The fix is quite easy, changing the build setting Require Only App-Extension-Safe API
to Yes
solves it.
I also wonder why UIKit is imported in the umbrella-header file and not Foundation, where the FOUNDATION_EXPORT
is declared and there seems to be no other dependency on UIKit?
I recall some issue with Swift's implicit typing and the overloading of then
. It seems that because then
is so overloaded, we end up having to explicitly type some closures.
I was thinking that it would help the type system and make our code more expressive if the then
overloads were replaced with map
and flatMap
as appropriate.
One con for this change would be that it moves away from the A+ spec, but we aren't programming in javascript so I don't think that should be an issue. Another con would be that this would be a breaking change. Maybe both could be implemented with then
deprecated for a version?
If this suggestion is acceptable, I would be happy to update the code... What say you?
There haven't been a lot of implementations' changes
There hasn't been a single API change
But still, there is 30+ commits to master since Feb, so I thought maybe it would be nice to bundle all the changes, especially getting that a public beta version of Xcode 10 is already out.
Hi,
I've been using your library, and it's been fantastic so far. However, recently I came across a use case where I needed something like allSettled from JavaScript.
I came up with the following extension:
extension Promises {
public static func allSettled<T>(_ promises: [Promise<T>]) -> Promise<[T?]> {
return Promises.all(promises.map { promise in
return Promise { fulfill, reject in
promise.then { value in
fulfill(value)
}.catch { _ in
fulfill(nil)
}
}
})
}
}
This uses optional types to resolve an array of promises to either their value, or nil
if the promise fails.
This can be useful if we need to wait for many actions to complete but don't care if some fail.
For instance, let's say we are loading 100 images for a social media feed. If some of the images fail to load, we don't want the entire operation to fail - we would rather display the images that did load, and place some placeholder for the ones that failed. This can easily be done with optional unwrapping, letting us deal with the error handling at a later stage.
The extension I wrote works well for me, but it would be nice to have something like this in the official API - my code is probably far from optimal anyway ๐.
$ swift build
Updating https://github.com/khanlou/Promise.git
Cloning https://github.com/khanlou/Promise.git
Resolving https://github.com/khanlou/Promise.git at 2.0.1
error: invalid target name at 'PromiseTests'; name of non-test targets cannot end in 'Tests'
swift-4.0.3-RELEASE-ubuntu16.10
Lubuntu 16.10 AMD64
The issue makes impossible to build with and link against this library while maintaining project using Swift Package Manager. Was not tested on macOS.
This is not the best place to ask the question, but the experts are here so here goes:
If I want to chain multiple network calls, how would I go about it? Each call has a different set of parameters and returns a Promise<[String:Any]>
Ideally, I would just store the results of each .then closure and simply return the next call:
typealias GetPromise = Promise<[String:Any]>
getDomains(baseDomain: credentials.baseDomain).then({ domains in
self.domains = domains
return self.getConfiguration(baseDomain: credentials.baseDomain, brandId: credentials.brandId)
}).then({ configuration in
self.configuration = configuration
return self.getServerTime(baseDomain: credentials.baseDomain)
}).then({ serverTime in
self.serverTime = serverTime
resultsBlock(Results.success)
}).catch { (error) in
resultsBlock(Results.error(error))
}
Unfortunately that violates the non-void return value in voice function rule.
So then I tried to return the results of each call with the promise in the next as a tuple, but that caused destructuring issues:
typealias GetPromise = Promise<[String:Any]>
getDomains(baseDomain: credentials.baseDomain).then({ domains in
return (domains, self.getConfiguration(baseDomain: credentials.baseDomain, brandId: credentials.brandId))
}).then({ (domains, configuration) in
return (domains, configuration, self.getServerTime(baseDomain: credentials.baseDomain))
}).then({ (domains, configuration, serverTime) in
resultsBlock(Results.succes)
}).catch { (error) in
resultsBlock(Results.error(error))
}
Here are my method definitions:
private func getDomains(baseDomain: String) -> GetPromise {
return get(urlPath: discoveryPath)
}
private func getConfiguration(baseDomain: String, brandId: String) -> GetPromise {
return get(urlPath: configurationPath)
}
private func getServerTime(baseDomain: String) -> GetPromise {
return get(urlPath: serverTimePath)
}
private func get(urlPath: String) -> GetPromise {
return GetPromise(work: { fulfill, reject in
guard let url = URL(string: urlPath) else {
reject(ConnectionManager.basicError)
return
}
let task = URLSession.shared.dataTask(with: url) {(data, _, error) in
if let error = error {
reject(error)
return
}
if let data = data
, let jsonObject = try? JSONSerialization.jsonObject(with: data, options: [])
, let dictValue = jsonObject as? [String:Any] {
fulfill(dictValue)
return
}
reject(ConnectionManager.basicError)
}
task.resume()
})
}
Any ideas?
I tried to use the cocoapods install and got something else completely different.
then
clause comes in 3 different variants
public func then<NewValue>(on queue: ExecutionContext = default, _ onFulfilled: @escaping (Value) throws -> Promise.Promise<NewValue>) -> Promise.Promise<NewValue>
public func then<NewValue>(on queue: ExecutionContext = default, _ onFulfilled: @escaping (Value) throws -> NewValue) -> Promise.Promise<NewValue>
public func then(on queue: ExecutionContext = default, _ onFulfilled: @escaping (Value) -> (), _ onRejected: @escaping (Error) -> () = default) -> Promise.Promise<Value>
This allows for promise chaining, one of the core concepts of promises, a feature of Javascript promises.
However, for some reason there is no such option for catch
clauses and there is only one variant
public func `catch`(on queue: ExecutionContext = default, _ onRejected: @escaping (Error) -> ()) -> Promise.Promise<Value>
What is the opinion about this functionality? Seems pretty core for me, should be added at some point. @khanlou what can you say?
In the the examples under Ensure and Throwing you write: URLSession.shared.dataTask(with: request)
, but the dataTask itself is not a promise. Did you mean to use the data task wrapping promise created under Creating Promises?
promise
.ensure({ data, httpResponse in
return httpResponse.statusCode == 200
})
.then({ data, httpResponse in
// the statusCode is valid
})
.catch({ error in
// the network request failed, or the status code was invalid
})
Would it be possible to get a new release added on so that the pod repo is up to date with some of the latest changes? The current iteration shows a compiler warning in later versions of swift due to compactMap
which has been fixed in the repo but not pushed out. Thanks!
Hi @khanlou - first of all, thanks for all the effort putting this library together!
I've come across a potential issue which seems like a race condition inside the library. Here is code for a test:
class PromiseTests: XCTestCase {
func testChainingOntoFulfilledPromiseWithOutstandingCallbacks() {
let promise = Promise<String>()
var didCallAlways = false
let thenExpectation = expectation(description: "Did call 'then'")
let alwaysExpectation = expectation(description: "Did call 'always'")
promise.catch { error in
XCTAssertEqual(didCallAlways, false)
}.then { value in
XCTAssertEqual(didCallAlways, false)
thenExpectation.fulfill()
}
promise.fulfill("Hello World")
promise.always {
didCallAlways = true
alwaysExpectation.fulfill()
}
wait(for: [thenExpectation, alwaysExpectation])
}
}
Which intermittently fails inside
.then { value in
XCTAssertEqual(didCallAlways, false)
thenExpectation.fulfill()
}
on
XCTAssertEqual(didCallAlways, false)
So, the scenario here is chaining something to the end of a fulfilled promise which has outstanding tasks chained on the end. From what I can understand stepping through the source code, I think the order in which the the "catch" & "then" tasks vs the "always" task executes is not guaranteed.
My assumption in this case was that the "always" should be queued behind the outstanding "catch" and "then" tasks.
I'm not sure if you'd consider this a bug, or a misuse of the API or something else, so thought I would raise it for discussion
Cheers
Hi,
I just discovered an issue with the callbacks. Please run my test a couple of times in order to see what's going on (sometimes it'll fail, but sometimes it won't):
func testQueues() {
var then: DispatchTime!
var always: DispatchTime!
weak var expectation = self.expectation(description: "Threading is hard!")
Promise(value: 1)
.then(on: DispatchQueue.global(qos: .default), { value in
return 2
})
.then(on: DispatchQueue.global(qos: .background)) { value -> Promise<Int> in
return Promise<Int>(queue: .main) { s, f in
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
s(3)
}
}
}
.then(on: DispatchQueue.global(qos: .background)) { value in
return 4
}
.then(on: DispatchQueue.global(qos: .utility), { value in
then = DispatchTime.now()
})
.catch(on: DispatchQueue.global(qos: .default)) { err in
// do nothing, won't fail
}
.then(on: DispatchQueue.global(qos: .default)) { value in
always = DispatchTime.now()
XCTAssert(then != nil)
XCTAssert(always > then)
expectation?.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
}
The source of this problem is here:
private func fireCallbacksIfCompleted() {
lockQueue.async(execute: {
guard !self.state.isPending else { return }
self.callbacks.forEach { callback in
switch self.state {
case let .fulfilled(value):
callback.callFulfill(value)
case let .rejected(error):
callback.callReject(error)
default:
break
}
}
self.callbacks.removeAll()
})
}
This method won't wait for the previous callback to finish, so if the actual execution is on multiple threads, the callback order can be messed up. It's a pretty nasty bug. ๐๐
I'd suggest to use a DispatchGroup so you could get notified after a callback finished it's work. Also If you have a better alternative in order to fix this, please let me know. Thanks! ๐
Given:
func verify(userId: String, withToken: String) -> Promise<Void> {
Promise<Void>(work: { [self] fulfill, reject in
let networking = makeClient(headers: ["X-Application-Publishable-Key" : apiKey])
networking.post("/api/users/\(userId)/verify/", parameters: ["token" : withToken]) { result in
switch result {
case .success(let response):
fulfill() <---- PROBLEM HERE
case .failure(let response):
//unpack the error etc
reject(clientError)
}
}
})
}
Results in: Missing argument for parameter #1 in call
. Did I miss something? Just coming back to Swift after dabbling in other languages.
The macOS deployment target is unset and seems to default to 10.13. Iโm trying to integrate the framework via Carthage and got a compile error because my app has a 10.11 deployment target. Are you aware of this issue or maybe Iโm doing something wrong. What would the minimum deployment target on Mac be? I guess the minimum version Swift 3.2 runs on. I could not find any info about this.
@khanlou I simply wanted to thank you for crafting such an excellent Promise library.
The target membership is not set on the Promise.swift
and Promise+Extras.swift
files. I had some issues integrating the framework via Carthage and this was one of the things I did to fix it. Makes sense to me that the files need the target membership for the mac library to work, but I might be missing something.
Promise.all
only works with Promises of one type. It would be nice to have similar helpers for Promises of different types, too. I think the only way to accomplish it is to have a separate method for each arity. I didn't know if you're interested in including a set of methods like that, so I decided to start by filing an issue instead of going directly for a PR.
I solved it like this:
public static func join<T1, T2>(_ p1: Promise<T1>, _ p2: Promise<T2>) -> Promise<(T1, T2)> {
return Promise<(T1, T2)>(work: { fulfill, reject in
p1.then({ value in
if let p2val = p2.value {
fulfill((value, p2val))
}
}).catch(reject)
p2.then({ value in
if let p1val = p1.value {
fulfill((p1val, value))
}
}).catch(reject)
})
}
public static func join<T1, T2, T3>(_ p1: Promise<T1>, _ p2: Promise<T2>, _ last: Promise<T3>) -> Promise<(T1, T2, T3)> {
return Promise<(T1, T2, T3)>(work: { fulfill, reject in
let joined = join(p1, p2)
func callFulfill(_ joinedVal: (T1, T2), _ lastVal: T3) -> Void {
fulfill((joinedVal.0, joinedVal.1, lastVal))
}
joined.then({ value in
if let lastval = last.value {
callFulfill(value, lastval)
}
}).catch(reject)
last.then({ value in
if let jval = joined.value {
callFulfill(jval, value)
}
}).catch(reject)
})
}
I generated the three parameter version with this:
func types(_ n: Int) -> String {
return (1...n).map { "T\($0)" }.joined(separator: ", ")
}
func createJoin(_ n: Int) -> String {
var output: String = "public static func join<"
output.append(types(n))
output.append(">(")
output.append((1...n-1).map { "_ p\($0): Promise<T\($0)>, " }.joined())
output.append("_ last: Promise<T\(n)>) -> Promise<(\(types(n)))> {\n")
output.append("return Promise<(\(types(n)))>(work: { fulfill, reject in\n")
output.append("let joined = join(")
output.append((1...n-1).map { "p\($0)" }.joined(separator: ", "))
output.append(")\n\n")
output.append("func callFulfill(_ joinedVal: (\(types(n-1))), _ lastVal: T\(n)) -> Void {\n")
output.append("fulfill((")
output.append((0...n-2).map { "joinedVal.\($0), " }.joined())
output.append("lastVal))\n")
output.append("}\n\n")
["joined.then({ value in",
"if let lastval = last.value {",
"callFulfill(value, lastval)",
"}",
"}).catch(reject)",
"last.then({ value in",
"if let jval = joined.value {",
"callFulfill(jval, value)",
"}",
"}).catch(reject)"].forEach { line in
output.append(line)
output.append("\n")
}
output.append("})\n}")
return output
}
print(createJoin(3))
So you could use that to generate methods for any arity N โฅ 3, as long as you also had generated ones for 3โฆN.
Should I file a pull request with something based on this?
The issue is discussed in commets to #43 . I will attempt to confirm this behavior today by attempting to compile master with the older toolchain. However the issue has been observed by the original PR creator when Travis said that there is no such function as compactMap
since it's been added in Swift 4.1's stdlib. Overall, the #43 has essentially destroyed all the backwards compatibility that there could be.
I am playing around with SwiftUI and stated getting the error
Generic enum 'State' cannot be used as an attribute.
on the @State
property wrapper due to an enum
called State
in the Promise library.
I had to distinguish the property wrapper by naming it @SwiftUI.State
to make the error go away.
First of all, thanks for this nice and lite library!
I think that if you integrate in the readme documentation the article that you wrote here http://khanlou.com/2016/08/common-patterns-with-promises/ is a plus for everyone that want to use it.
;)
The test target is not configured in Swift Package Manager environment.
Furthermore, there is a certain degree of suspicion that Tests wouldn't even build for Linux because of not yet implemented parts of Swift foundation (not Foundation framework, but whole arrangement of tools and libs) being used... Possibly.
Would be cool to make a tag (maybe v0.1
) so I could grab the code via Carthage!
Since static libraries are now functioning properly with Swift and it's a release feature, why wouldn't we add
a static library target to the podspec?
This looks like a swift compiler method matching bug, but a promise holding Any
(e.g. used for JSON parsing) will not be used with the flatMap
but the map
variant of the then
method, resulting in the promise object itself being treated as the value. See this simple snippet that can be pasted into a test class:
func promiseAny() -> Promise<Any> {
return Promise<Any> { fulfill, _ in
fulfill("a")
}
}
func testMatch() {
let exp = expectation(description: "")
Promise<Int>(value: 1).then { _ in
return self.promiseAny()
}.then { value in
XCTAssert(value == "a")
exp.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
}
value == "a"
does not compile because the compiler thinks value is of type Promise<Any>
where it should be Any
in my opinion. Changing the promiseAny
function to return Promise<String>
makes it work, though.
Maybe this is a known swift issue or maybe this is how it should work and I have it backwards, but I think this could be pointed out in the README. Or maybe you even know a way to fix it :)
Otherwise thanks for this great library. I looked at some other swift promises libs today and only this one works the way I expected it to.
As a response to issues discussed in #45.
TravisCI is as commonly known doesn't work with Swift because of some issues with multi targeted Objective-C builds in Xcode or whatever...
And also it has come to my attention that in 7b9764c the Travis badge has been removed altogether, which makes me think that its absence is commonly tolerated and accepted :)
Suggestion: @khanlou should log in at public https://travis-ci.org and disable automatic builds for this repository altogether, so it doesn't show that build failed for literally every single commit and pull-request.
I've set up a somewhat complex Promise chain where the catch clause is not invoked as expected.
Here's a test-case for Promise
(fails) and another for PromiseKit
(passes).
Test case is derived from a real world app scenario that didn't work as I expected it would. Perhaps it is a user error or misunderstanding.
Failing Test:
import Foundation
import Promise
class Chain {
func verify(userId: String, withToken: String) -> Promise<Void> {
chunk1()
.then { [self] result in
chunk2()
}
.then {
print("All done!")
}
}
func chunk1() -> Promise<String> {
first()
.then { [self] result in
second()
}
.then { [self] result in
third()
}
.then { [self] result in
fourth()
}
}
func chunk2() -> Promise<String> {
fifth()
.then { [self] result in
sixth()
}
.then { [self] result in
seventh()
}
.then { [self] result in
eighth()
}
}
func first() -> Promise<String> {
Promise<String>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 1"
print(message)
fulfill(message)
}
})
}
func second() -> Promise<String> {
Promise<String>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 2"
print(message)
fulfill(message)
}
})
}
func third() -> Promise<String> {
Promise<String>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 3"
print(message)
fulfill(message)
}
})
}
func fourth() -> Promise<String> {
Promise<String>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 4"
print(message)
fulfill(message)
}
})
}
func fifth() -> Promise<String> {
Promise<String>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 5"
print(message)
fulfill(message)
}
})
}
func sixth() -> Promise<String> {
Promise<String>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 6"
print(message)
reject(RuntimeError(reason: "bang at 6"))
}
})
}
func seventh() -> Promise<String> {
Promise<String>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 7"
print(message)
fulfill(message)
}
})
}
func eighth() -> Promise<String> {
Promise<String>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 8"
print(message)
fulfill(message)
}
})
}
func ninth() -> Promise<String> {
Promise<String>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 9"
print(message)
fulfill(message)
}
})
}
}
Failing test:
class PromiseTest : XCTestCase {
func test_it_handles_chain() {
var result: Bool? = nil
let chain = Chain()
chain.verify(userId: "foo", withToken: "foo")
.then {
result = false
}
.catch { error in
result = true
}
expect {
result != nil
}
print("Go the final result: \(result)")
XCTAssertEqual(result, true)
}
}
class PKChain {
func verify(userId: String, withToken: String) -> Promise<Void> {
chunk1()
.then { [self] result in
chunk2()
}
.done { result in
print("All done!")
}
}
func chunk1() -> Promise<String> {
first()
.then { [self] result in
second()
}
.then { [self] result in
third()
}
.then { [self] result in
fourth()
}
}
func chunk2() -> Promise<String> {
fifth()
.then { [self] result in
sixth()
}
.then { [self] result in
seventh()
}
.then { [self] result in
eighth()
}
}
func first() -> Promise<String> {
Promise { seal in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 1"
print(message)
seal.fulfill(message)
}
}
}
func second() -> Promise<String> {
Promise { seal in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 2"
print(message)
seal.fulfill(message)
}
}
}
func third() -> Promise<String> {
Promise { seal in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 3"
print(message)
seal.fulfill(message)
}
}
}
func fourth() -> Promise<String> {
Promise { seal in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 4"
print(message)
seal.fulfill(message)
}
}
}
func fifth() -> Promise<String> {
Promise { seal in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 5"
print(message)
seal.fulfill(message)
}
}
}
func sixth() -> Promise<String> {
Promise { seal in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 6"
print(message)
seal.reject(RuntimeError(reason: "bang at 6"))
}
}
}
func seventh() -> Promise<String> {
Promise { seal in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 7"
print(message)
seal.fulfill(message)
}
}
}
func eighth() -> Promise<String> {
Promise { seal in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 8"
print(message)
seal.fulfill(message)
}
}
}
func ninth() -> Promise<String> {
Promise { seal in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let message = "message 9"
print(message)
seal.fulfill(message)
}
}
}
}
Test case:
class PKPromisesTest : XCTestCase {
func test_it_handles_chain() {
var result: Bool? = nil
let chain = PKChain()
chain.verify(userId: "foo", withToken: "foo")
.done { string in
result = false
}
.catch { error in
print("Ahahahahah error: \(error)")
result = true
}
expect {
result != nil
}
print("Go the final result: \(result)")
XCTAssertEqual(result, true)
}
}
Source for Expect/Async Function:
(Sometimes I find XCTest style expectations fussy, so I roll this one:)
func expect(condition: @escaping () -> Bool) {
expectWithin(seconds: 7, condition: condition)
}
func expectWithin(seconds: TimeInterval, condition: @escaping () -> Bool) {
for _ in 0..<Int(seconds) {
if condition() {
return
} else {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 1))
}
}
fatalError("Condition didn't happen before timeout:\(seconds)")
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.