A Promise library for Swift, based partially on Javascript's A+ spec

Related tags

EventBus Promise
Overview

Promise

A Promise library for Swift, based partially on Javascript's A+ spec.

What is a Promise?

A Promise is a way to represent a value that will exist (or will fail with an error) at some point in the future. This is similar to how an Optional represents a value that may or may not be there.

Using a special type to represent values that will exist in the future means that those values can be combined, transformed, and built in systematic ways. If the system knows what success and what failure look like, composing those asynchronous operations becomes much easier. For example, it becomes trivial to write reusable code that can:

  • perform a chain of dependent asynchronous operations with one completion block at the end
  • perform many independent asynchronous operations simultaneously with one completion block
  • race many asynchronous operations and return the value of the first to complete
  • retry asynchronous operations
  • add a timeout to asynchronous operations

Promises are suited for any asynchronous action that can succeed or fail exactly once, such as HTTP requests. If there is an asynchronous action that can "succeed" more than once, or delivers a series of values over time instead of just one, take a look at Signals or Observables.

Basic Usage

To access the value once it arrives, you call the then method with a block.

let usersPromise = fetchUsers() // Promise<[User]>
usersPromise.then({ users in
    self.users = users
})

All usage of the data in the users Promise is gated through the then method.

In addition to performing side effects (like setting the users instance variable on self), then enables you do two other things. First, you can transform the contents of the Promise, and second, you can kick off another Promise, to do more asynchronous work. To do either of these things, return something from the block you pass to then. Each time you call then, the existing Promise will return a new Promise.

let usersPromise = fetchUsers() // Promise<[User]>
let firstUserPromise = usersPromise.then({ users in // Promise<User>
    return users[0]
})
let followersPromise = firstUserPromise.then({ firstUser in //Promise<[Follower]>
    return fetchFollowers(of: firstUser)
})
followersPromise.then({ followers in
    self.followers = followers
})

Based on whether you return a regular value or a promise, the Promise will determine whether it should transform the internal contents, or fire off the next promise and await its results.

As long as the block you pass to then is one line long, its type signature will be inferred, which will make Promises much easier to read and work with.

Since each call to then returns a new Promise, you can write them in a big chain. The code above, as a chain, would be written:

fetchUsers()
    .then({ users in
        return users[0]
    })
    .then({ firstUser in
        return fetchFollowers(of: firstUser)
    })
    .then({ followers in
        self.followers = followers
    })

To catch any errors that are created along the way, you can add a catch block as well:

fetchUsers()
    .then({ users in
        return users[0]
    })
    .then({ firstUser in
        return fetchFollowers(of: firstUser)
    })
    .then({ followers in
        self.followers = followers
    })
    .catch({ error in
        displayError(error)
    })

If any step in the chain fails, no more then blocks will be executed. Only failure blocks are executed. This is enforced in the type system as well. If the fetchUsers() promise fails (for example, because of a lack of internet), there's no way for the promise to construct a valid value for the users variable, and there's no way that block could be called.

Creating Promises

To create a promise, there is a convenience initializer that takes a block and provides functions to fulfill or reject the promise:

let promise = Promise<Data>(work: { fulfill, reject in
    try fulfill(Data(contentsOf: someURL)
})

It will automatically run on a global background thread.

You can use this initializer to wrap a completion block-based API, like URLSession.

let promise = Promise<(Data, HTTPURLResponse)>(work: { fulfill, reject in
    self.dataTask(with: request, completionHandler: { data, response, error in
        if let error = error {
            reject(error)
        } else if let data = data, let response = response as? HTTPURLResponse {
            fulfill((data, response))
        } else {
            fatalError("Something has gone horribly wrong.")
        }
    }).resume()
})

If the API that you're wrapping is sensitive to which thread it's being run on, like any UIKit code, be sure to pass add a queue: .main parameter to the work: initializer, and it will be executed on the main queue.

For delegate-based APIs, you can can create a promise in the .pending state with the default initializer.

let promise = Promise()

and use the fulfill and reject instance methods to change its state.

Advanced Usage

Because promises formalize how success and failure blocks look, it's possible to build behaviors on top of them.

always

For example, if you want to execute code when the promise fulfills — regardless of whether it succeeds or fails – you can use always.

activityIndicator.startAnimating()
fetchUsers()
    .then({ users in
        self.users = users
    })
    .always({
        self.activityIndicator.stopAnimating()
    })

Even if the network request fails, the activity indicator will stop. Note that the block that you pass to always has no parameters. Because the Promise doesn't know if it will succeed or fail, it will give you neither a value nor an error.

ensure

ensure is a method that takes a predicate, and rejects the promise chain if that predicate fails.

URLSession.shared.dataTask(with: request)
    .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
    })

Static methods

Static methods, like zip, race, retry, all, kickoff live in a namespace called Promises. Previously, they were static functions in the Promise class, but this meant that you had to specialize them with a generic before you could use them, like Promise<()>.all. This is ugly and hard to type, so as of v2.0, you now write Promises.all.

all

Promises.all is a static method that waits for all the promises you give it to fulfill, and once they have, it fulfills itself with the array of all fulfilled values. For example, you might want to write code to hit an API endpoint once for each item in an array. map and Promises.all make that super easy:

let userPromises = users.map({ user in
    APIClient.followUser(user)
})
Promises.all(userPromises)
    .then({
        //all the users are now followed!
    })
    .catch  ({ error in
        //one of the API requests failed
    })

kickoff

Because the then blocks of promises are a safe space for functions that throw, its sometimes useful to enter those safe spaces even if you don't have asynchronous work to do. Promises.kickoff is designed for that.

Promises
	.kickoff({
		return try initialValueFromThrowingFunction()
	})
	.then({ value in
		//use the value from the throwing function
	})
	.catch({ error in
		//if there was an error, you can handle it here.
	})

This (coupled with Optional.unwrap()) is particularly useful when you want to start a promise chain from an optional value.

Others behaviors

These are some of the most useful behaviors, but there are others as well, like race (which races multiple promises), retry (which lets you retry a single promise multiple times), and recover (which lets you return a new Promise given an error, allowing you to recover from failure), and others.

You can find these behaviors in the Promises+Extras.swift file.

Invalidatable Queues

Each method on the Promise that accepts a block accepts an execution context with the parameter name on:. Usually, this execution context is a queue.

Promise<Void>(queue: .main, work: { fulfill, reject in
    viewController.present(viewControllerToPresent, animated: flag, completion: {
        fulfill()
    })
}).then(on: DispatchQueue.global(), {
	return try Data(contentsOf: someURL)
})

Because ExecutionContext is a protocol, other things can be passed here. One particularly useful one is InvalidatableQueue. When working with table cells, often the result of a promise needs to be ignored. To do this, each cell can hold on to an InvalidatableQueue. An InvalidatableQueue is an execution context that can be invalidated. If the context is invalidated, then the block that is passed to it will be discarded and not executed.

To use this with table cells, the queue should be invalidated and reset on prepareForReuse().

class SomeTableViewCell: UITableViewCell {
    var invalidatableQueue = InvalidatableQueue()
        
    func showImage(at url: URL) {
        ImageFetcher(url)
            .fetch()
            .then(on: invalidatableQueue, { image in
                self.imageView.image = image
            })
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        invalidatableQueue.invalidate()
        invalidatableQueue = InvalidatableQueue()
    }

}

Warning: don't chain blocks off anything that is executing on an invalidatable queue. then blocks that return Void won't stop the chain, but then blocks that return values or promises will stop the chain. Because the block can't be executed, the result of the next value in the chain won't be calculable, and the next promise will remain in the pending state forever, preventing resources from being released.

Ease of Use

I made several design decisions when writing this Promise library, erring towards making the library as easy to use as possible.

Simplified Naming

Other promise libraries use the functional names for then, such as map and flatMap. The benefit to using these monadic functional terms is minor, but cost, in terms of understanding, is high. In this library, you call then, and return anything you need to, and the library figures out how to handle it.

Error Parameterization

Other promise libraries allow you to define what type the error each promise will return. In theory, this a useful feature, allowing you to know what type the error will be in catch blocks.

In practice, this is stifling. In practice, if you're using errors from two different domains, you have to either a) use a lowest common denominator error, like NSError, or b) call a method like mapError to convert the error from one domain to another.

Also note that Swift's built-in error handling system doesn't have typed errors, opting for pattern matching instead.

Throwing

Lastly, you can use try and throw from within all the blocks, and the library will automatically translate it to a promise rejection. This makes working with APIs that throw much more easily. To extend our URLSession example, we can use the throwing JSONSerialization API easily.

URLSession.shared.dataTask(with: request)
    .ensure({ data, httpResponse in httpResponse.statusCode == 200 })
    .then({ data, httpResponse in
        return try JSONSerialization.jsonObject(with: data)
    })
    .then({ json in
        // use the json
    })

Working with optionals can be made simpler with a little extension.

struct NilError: Error { }

extension Optional {
    func unwrap() throws -> Wrapped {
        guard let result = self else {
            throw NilError()
        }
        return result
    }
}

Because you're in an environment where you can freely throw and it will be handled for you (in the form of a rejected Promise), you can now easily unwrap optionals. For example, if you need a specific key out of a json dictionary:

.then({ json in
    return try (json["user"] as? [String: Any]).unwrap()
})

And you will transform your optional into a non-optional.

Threading Model

The threading model for this library is dead simple. init(work:) happens on a background queue by default, and every other block-based method (then, catch, always, etc) executes on the main thread. These can be overridden by passing in a DispatchQueue object for the first parameter.

Promise<Void>(work: { fulfill, reject in
    viewController.present(viewControllerToPresent, animated: flag, completion: {
        fulfill()
    })
}).then(on: DispatchQueue.global(), {
	return try Data(contentsOf: someURL)
}).then(on: DispatchQueue.main, {
	self.data = data
})

Installation

CocoaPods

  1. Add the following to your Podfile:

    pod 'Promises'
  2. Integrate your dependencies using frameworks: add use_frameworks! to your Podfile.

  3. Run pod install.

Playing Around

To get started playing with this library, you can use the included Promise.playground. Simply open the .xcodeproj, build the scheme, and then open the playground (from within the project) and start playing.

Comments
  • adds support for CocoaPods and Swift PM

    adds support for CocoaPods and Swift PM

    I've added the necessary support for CocoaPods and SwiftPM.

    • [ ] Retain name of Promise on CocoaPods or change to a unique name
    • [ ] Merge PR to master
    • [ ] Run the following commands below successfully
    git tag 2.0.0
    git push --tags
    bundle install
    bundle exec pod spec lint
    bundle exec pod trunk push
    
    opened by brennanMKE 14
  • `then` methods type inferring issue

    `then` methods type inferring issue

    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.

    Possible solution

    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.

    opened by isaac-weisberg 13
  • Tests while in SPM

    Tests while in SPM

    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.

    opened by isaac-weisberg 10
  • How to switch catches on error types?

    How to switch catches on error types?

    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:

    afbeelding

    (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

    opened by epologee 9
  • Convert to Swift 3

    Convert to Swift 3

    Using Xcode 8.0 GM seed (8A218a)

    Swift 3 changes:

    • Swift 3 API changes (especially with Dispatch)
    • Change capitalization of enum cases
    • Add @discardableResult to methods that need it
    • Add @escaping to closure parameters
    • Rename args for zip(), to be more like the Swift 3 API design guidelines (I think)

    Xcode 8 changes:

    • Let Xcode do suggested build setting updates

    Not strictly Swift 3-related:

    • Tweak some headerdocs, and split State into extensions
    opened by kelan 8
  • Catch Clause Not Invoked

    Catch Clause Not Invoked

    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)
    
        }
    
    }
    

    PromiseKit Version

    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)")
    }
    
    opened by jasperblues 7
  • Incorrect example code in Readme

    Incorrect example code in Readme

    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
      })
    
    opened by SintraWorks 7
  • Add some helper methods that are useful when dealing with errors

    Add some helper methods that are useful when dealing with errors

    These are some of the additions we've made in our private copy of this library that allow for transforming an error if needed, that add convenient ways to reject or construct a rejected promise, and that adds a GeneralError type to assist with those new ways to reject a promise.

    opened by klundberg 6
  • Update Podspec

    Update Podspec

    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!

    opened by pushchris 6
  • TravisCI making it look like it works

    TravisCI making it look like it works

    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 7b9764c397f8ba992cb4103d9e900d3d885c5e90 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.

    opened by isaac-weisberg 6
  • Add tvOS and watchOS deployment targets

    Add tvOS and watchOS deployment targets

    Obligatory Office GIF

    dxocwugdzryg4

    What

    Adds tvOS and watchOS targets and made the schemes shared so Carthage can access them. Also added deployment targets to the podspec

    The Why

    TL;DR I was working on this: https://github.com/feathersjs-ecosystem/feathers-swift/pull/29 and saw this awesome library but it didn't have tvOS/watchOS support for Carthage so I added it! But also because I said so!

    Pending

    One other change I'd like to see/might put a PR up for is adding testing targets for each deployment target. They'd all point to the same testing bundle and they'd let each scheme run the tests themselves which ensures tests pass on any given OS. And also Linux support would be cool, down to put a PR up for that as well.

    opened by thebarndog 5
  • Bump tzinfo from 1.2.5 to 1.2.10

    Bump tzinfo from 1.2.5 to 1.2.10

    Bumps tzinfo from 1.2.5 to 1.2.10.

    Release notes

    Sourced from tzinfo's releases.

    v1.2.10

    TZInfo v1.2.10 on RubyGems.org

    v1.2.9

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    TZInfo v1.2.9 on RubyGems.org

    v1.2.8

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    TZInfo v1.2.8 on RubyGems.org

    v1.2.7

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    TZInfo v1.2.7 on RubyGems.org

    v1.2.6

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.

    TZInfo v1.2.6 on RubyGems.org

    Changelog

    Sourced from tzinfo's changelog.

    Version 1.2.10 - 19-Jul-2022

    Version 1.2.9 - 16-Dec-2020

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    Version 1.2.8 - 8-Nov-2020

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    Version 1.2.7 - 2-Apr-2020

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    Version 1.2.6 - 24-Dec-2019

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.
    Commits
    • 0814dcd Fix the release date.
    • fd05e2a Preparing v1.2.10.
    • b98c32e Merge branch 'fix-directory-traversal-1.2' into 1.2
    • ac3ee68 Remove unnecessary escaping of + within regex character classes.
    • 9d49bf9 Fix relative path loading tests.
    • 394c381 Remove private_constant for consistency and compatibility.
    • 5e9f990 Exclude Arch Linux's SECURITY file from the time zone index.
    • 17fc9e1 Workaround for 'Permission denied - NUL' errors with JRuby on Windows.
    • 6bd7a51 Update copyright years.
    • 9905ca9 Fix directory traversal in Timezone.get when using Ruby data source
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Bump cocoapods-downloader from 1.2.2 to 1.6.3

    Bump cocoapods-downloader from 1.2.2 to 1.6.3

    Bumps cocoapods-downloader from 1.2.2 to 1.6.3.

    Release notes

    Sourced from cocoapods-downloader's releases.

    1.6.3

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1

    Enhancements
    • None.
    Bug Fixes
    • Fix "can't modify frozen string" errors when pods are integrated using the branch option
      buju77 #10920

    1.5.0

    ... (truncated)

    Changelog

    Sourced from cocoapods-downloader's changelog.

    1.6.3 (2022-04-01)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2 (2022-03-28)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1 (2022-03-23)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0 (2022-03-22)

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1 (2021-09-07)

    Enhancements
    • None.

    ... (truncated)

    Commits
    • c03e2ed Release 1.6.3
    • f75bccc Disable Bazaar tests due to macOS 12.3 not including python2
    • 52a0d54 Merge pull request #128 from CocoaPods/validate_before_dl
    • d27c983 Ensure that the git pre-processor doesn't accidentally bail also
    • 3adfe1f [CHANGELOG] Add empty Master section
    • 591167a Release 1.6.2
    • d2564c3 Merge pull request #127 from CocoaPods/validate_before_dl
    • 99fec61 Switches where we check for invalid input, to move it inside the download fun...
    • 96679f2 [CHANGELOG] Add empty Master section
    • 3a7c54b Release 1.6.1
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Race condition when chaining a promise after being fulfilled?

    Race condition when chaining a promise after being fulfilled?

    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

    opened by DNCGraef 1
  • Suggestion: AllSettled implementation

    Suggestion: AllSettled implementation

    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.

    Use Case

    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 😊.

    opened by hannessolo 5
Releases(2.0.1)
Owner
Soroush Khanlou
Soroush Khanlou
Promise + progress + pause + cancel + retry for Swift.

SwiftTask Promise + progress + pause + cancel + retry for Swift. How to install See ReactKit Wiki page. Example Basic // define task let task = Task<F

ReactKit 1.9k Dec 27, 2022
Promise/A+, Bluebird inspired, implementation in Swift 5

Bluebird.swift Promise/A+ compliant, Bluebird inspired, implementation in Swift 5 Features Promise/A+ Compliant Swift 5 Promise Cancellation Performan

Andrew Barba 41 Jul 11, 2022
A Swift based Future/Promises Library for IOS and OS X.

FutureKit for Swift A Swift based Future/Promises Library for IOS and OS X. Note - The latest FutureKit is works 3.0 For Swift 2.x compatibility use v

null 759 Dec 2, 2022
NotificationCenter based Lightweight UI / AnyObject binder.

Continuum NotificationCenter based Lightweight UI / AnyObject binder. final class ViewController: UIViewController { @IBOutlet weak var label: UI

Taiki Suzuki 82 Apr 4, 2021
FutureLib is a pure Swift 2 library implementing Futures & Promises inspired by Scala.

FutureLib FutureLib is a pure Swift 2 library implementing Futures & Promises inspired by Scala, Promises/A+ and a cancellation concept with Cancellat

Andreas Grosam 39 Jun 3, 2021
⚡️ Lightweight full-featured Promises, Async & Await Library in Swift

Lightweight full-featured Promises, Async & Await Library in Swift What's this? Hydra is full-featured lightweight library which allows you to write b

Daniele Margutti 2k Dec 31, 2022
A promises library written in Swift featuring combinators like map, flatMap, whenAll, whenAny.

Promissum is a promises library written in Swift. It features some known functions from Functional Programming like, map and flatMap. It has useful co

Tom Lokhorst 68 Aug 31, 2022
A library that adds a throwing unwrap operator in Swift.

ThrowingUnwrap A simple package to add a throwing unwrap operator (~!) to Optionals in Swift. Import Add this to the package-wide dependencies in Pack

Allotrope 3 Aug 27, 2022
Futures and Promises library

#PureFutures A simple Futures and Promises library. ##Installation ###Carthage Add the following in your Cartfile: github "wiruzx/PureFutures" And ru

Victor Shamanov 17 Apr 5, 2019
Promises for Swift & ObjC.

Promises simplify asynchronous programming, freeing you up to focus on the more important things. They are easy to learn, easy to master and result in

Max Howell 14k Jan 5, 2023
When is a lightweight implementation of Promises in Swift.

Description When is a lightweight implementation of Promises in Swift. It doesn't include any helper functions for iOS and OSX and it's intentional, t

Vadym Markov 260 Oct 12, 2022
Write great asynchronous code in Swift using futures and promises

BrightFutures How do you leverage the power of Swift to write great asynchronous code? BrightFutures is our answer. BrightFutures implements proven fu

Thomas Visser 1.9k Dec 20, 2022
The easiest Future and Promises framework in Swift. No magic. No boilerplate.

Promis The easiest Future and Promises framework in Swift. No magic. No boilerplate. Overview While starting from the Objective-C implementation of Ju

Alberto De Bortoli 111 Dec 27, 2022
Promises is a modern framework that provides a synchronization construct for Swift and Objective-C.

Promises Promises is a modern framework that provides a synchronization construct for Objective-C and Swift to facilitate writing asynchronous code. I

Google 3.7k Dec 24, 2022
Easy Swift Futures & Promises.

❗️ Archived now ❗️ Since Apple released Combine framework, I decide to archive this repo. You still can use this repo as an example of Future/Promise

Dmytro Mishchenko 40 Sep 23, 2022
Material para a apresentação da palestra "Implementando Interesses Transversais - um papo sobre arquitetura, DI e Design Patterns em Swift/iOS" no TDC Future 2021

--- title: Implementando Interesses Transversais - um papo sobre arquitetura, DI e Design Patterns em Swift/iOS author: Cícero Camargo date: Nov 30th

Cícero Camargo 2 Nov 30, 2021
PromiseKit 4.5.2 with changes for Swift 5

繁體中文, 简体中文 Promises simplify asynchronous programming, freeing you up to focus on the more important things. They are easy to learn, easy to master an

null 0 Dec 24, 2021
Swift µframework providing Future

Future [] (https://github.com/Carthage/Carthage) Swift µframework providing Future<T, Error>. This library is inspired by the talk of Javier Soto at S

Le Van Nghia 122 Jun 3, 2021
Swift implementation of the package url spec

PackageURL Swift implementation of the package url specification. Requirements Swift 5.3+ Usage import PackageURL let purl: PackageURL = "pkg:swift/a

Mattt 21 Jun 14, 2022
A light-weighted Promise library for Objective-C

RWPromiseKit Desiciption A light-weighted Promise library for Objective-C About Promise The Promise object is used for deferred and asynchronous compu

Canopus 113 May 4, 2022