From 41ed39ebc879a4a4a5fa5372edfe7bdcff31abbf Mon Sep 17 00:00:00 2001 From: Juri Pakaste Date: Sun, 5 Mar 2017 10:32:52 +0200 Subject: [PATCH 01/14] Move Promise.zip to Promises enum. Deprecate Promise.zip. --- Promise/Promise+Extras.swift | 9 ++++++++- PromiseTests/PromiseZipTests.swift | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Promise/Promise+Extras.swift b/Promise/Promise+Extras.swift index dcd730d..6a16232 100644 --- a/Promise/Promise+Extras.swift +++ b/Promise/Promise+Extras.swift @@ -104,8 +104,15 @@ extension Promise { }).then(fulfill).catch(reject) }) } - + + @available(*, deprecated, message: "Use Promises.zip instead") public static func zip(_ first: Promise, and second: Promise) -> Promise<(T, U)> { + return Promises.zip(first, second) + } +} + +public enum Promises { + public static func zip(_ first: Promise, _ second: Promise) -> Promise<(T, U)> { return Promise<(T, U)>(work: { fulfill, reject in let resolver: (Any) -> () = { _ in if let firstValue = first.value, let secondValue = second.value { diff --git a/PromiseTests/PromiseZipTests.swift b/PromiseTests/PromiseZipTests.swift index b9d1161..c5f07df 100644 --- a/PromiseTests/PromiseZipTests.swift +++ b/PromiseTests/PromiseZipTests.swift @@ -13,11 +13,11 @@ import Promise class PromiseZipTests: XCTestCase { func testZipping2() { - weak var expectation = self.expectation(description: "`Promise.zip` should be type safe.") - + weak var expectation = self.expectation(description: "`Promises.zip` should be type safe.") + let promise = Promise(value: 2) let promise2 = Promise(value: "some string") - let zipped = Promise<()>.zip(promise, and: promise2) + let zipped = Promises.zip(promise, promise2) zipped.always({ expectation?.fulfill() }) From c867af2437325f90975c5c6bcdee649b047cff6b Mon Sep 17 00:00:00 2001 From: Juri Pakaste Date: Sun, 5 Mar 2017 11:33:05 +0200 Subject: [PATCH 02/14] Add a playground page for generating zip functions --- .../Contents.swift | 56 +++++++++++++++++++ Promise.playground/contents.xcplayground | 1 + 2 files changed, 57 insertions(+) create mode 100644 Promise.playground/Pages/Zip Functions Generator.xcplaygroundpage/Contents.swift diff --git a/Promise.playground/Pages/Zip Functions Generator.xcplaygroundpage/Contents.swift b/Promise.playground/Pages/Zip Functions Generator.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000..2d6b882 --- /dev/null +++ b/Promise.playground/Pages/Zip Functions Generator.xcplaygroundpage/Contents.swift @@ -0,0 +1,56 @@ +//: [Previous](@previous) + +/*: + + # Zip Functions Generator + + This page generates `zip` functions for 3-*N* parameters. They all build on the + two parameter variant, with `zip` for *N* parameters always delegating work + to one with *N*-1 parameters. + + */ + +import Foundation + +func types(_ n: Int) -> String { + return (1...n).map { "T\($0)" }.joined(separator: ", ") +} + + +func createZip(_ n: Int) -> String { + var output: String = "/// Zips \(n) promises of different types into a single Promise whose\n" + output.append("/// type is a tuple of \(n) elements.\n") + output.append("public static func zip<") + output.append(types(n)) + output.append(">(") + output.append((1...n-1).map { "_ p\($0): Promise, " }.joined()) + output.append("_ last: Promise) -> Promise<(\(types(n)))> {\n") + + output.append("return Promise<(\(types(n)))>(work: { (fulfill: @escaping ((\(types(n)))) -> Void, reject: @escaping (Error) -> Void) in\n") + + output.append("let zipped: Promise<(\(types(n-1)))> = zip(") + output.append((1...n-1).map { "p\($0)" }.joined(separator: ", ")) + output.append(")\n\n") + + output.append("func resolver() -> Void {\n") + output.append("if let zippedValue = zipped.value, let lastValue = last.value {\n") + output.append("fulfill((") + output.append((0...n-2).map { "zippedValue.\($0), " }.joined()) + output.append("lastValue))\n}\n}\n") + + output.append("zipped.then({ _ in resolver() }, reject)\n") + output.append("last.then({ _ in resolver() }, reject)\n") + + output.append("})\n}") + + return output +} + +func createZips(_ n: Int) -> String { + return (3...n).map(createZip).joined(separator: "\n\n") +} + +print(createZips(6)) + + +//: [Next](@next) diff --git a/Promise.playground/contents.xcplayground b/Promise.playground/contents.xcplayground index 4f0a5a2..6b603f5 100644 --- a/Promise.playground/contents.xcplayground +++ b/Promise.playground/contents.xcplayground @@ -3,5 +3,6 @@ + \ No newline at end of file From b8844f14747f29f0699b6111ec4ec0262204c014 Mon Sep 17 00:00:00 2001 From: Juri Pakaste Date: Sun, 5 Mar 2017 11:33:28 +0200 Subject: [PATCH 03/14] Add zip functions for 3 and 4 parameters --- Promise/Promise+Extras.swift | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Promise/Promise+Extras.swift b/Promise/Promise+Extras.swift index 6a16232..51f4a67 100644 --- a/Promise/Promise+Extras.swift +++ b/Promise/Promise+Extras.swift @@ -123,4 +123,40 @@ public enum Promises { second.then(resolver, reject) }) } + + // The following zip functions have been created with the + // "Zip Functions Generator" playground page. If you need variants with + // more parameters, use it to generate them. + + /// Zips 3 promises of different types into a single Promise whose + /// type is a tuple of 3 elements. + public static func zip(_ p1: Promise, _ p2: Promise, _ last: Promise) -> Promise<(T1, T2, T3)> { + return Promise<(T1, T2, T3)>(work: { (fulfill: @escaping ((T1, T2, T3)) -> Void, reject: @escaping (Error) -> Void) in + let zipped: Promise<(T1, T2)> = zip(p1, p2) + + func resolver() -> Void { + if let zippedValue = zipped.value, let lastValue = last.value { + fulfill((zippedValue.0, zippedValue.1, lastValue)) + } + } + zipped.then({ _ in resolver() }, reject) + last.then({ _ in resolver() }, reject) + }) + } + + /// Zips 4 promises of different types into a single Promise whose + /// type is a tuple of 4 elements. + public static func zip(_ p1: Promise, _ p2: Promise, _ p3: Promise, _ last: Promise) -> Promise<(T1, T2, T3, T4)> { + return Promise<(T1, T2, T3, T4)>(work: { (fulfill: @escaping ((T1, T2, T3, T4)) -> Void, reject: @escaping (Error) -> Void) in + let zipped: Promise<(T1, T2, T3)> = zip(p1, p2, p3) + + func resolver() -> Void { + if let zippedValue = zipped.value, let lastValue = last.value { + fulfill((zippedValue.0, zippedValue.1, zippedValue.2, lastValue)) + } + } + zipped.then({ _ in resolver() }, reject) + last.then({ _ in resolver() }, reject) + }) + } } From 07d19c6b73138d3beee30dfa7e75c0b87d53f9aa Mon Sep 17 00:00:00 2001 From: Juri Pakaste Date: Sun, 5 Mar 2017 11:37:34 +0200 Subject: [PATCH 04/14] Add a test for multiple parameter zip --- PromiseTests/PromiseZipTests.swift | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/PromiseTests/PromiseZipTests.swift b/PromiseTests/PromiseZipTests.swift index c5f07df..d47d476 100644 --- a/PromiseTests/PromiseZipTests.swift +++ b/PromiseTests/PromiseZipTests.swift @@ -27,4 +27,27 @@ class PromiseZipTests: XCTestCase { XCTAssertEqual(tuple.0, 2) XCTAssertEqual(tuple.1, "some string") } + + func testMultipleParameters() { + // The >2 parameter variants of zip are all generated, so testing one + // *should* mean all others work too. + + weak var expectation = self.expectation(description: "`Promises.zip` should be type safe.") + + let promise = Promise(value: 2) + let promise2 = Promise(value: "some string") + let promise3 = Promise(value: [1, 1, 2, 3, 5]) + let promise4 = Promise(value: ["two", "strings"]) + let zipped = Promises.zip(promise, promise2, promise3, promise4) + zipped.always({ + expectation?.fulfill() + }) + + waitForExpectations(timeout: 1, handler: nil) + guard let tuple = zipped.value else { XCTFail(); return } + XCTAssertEqual(tuple.0, 2) + XCTAssertEqual(tuple.1, "some string") + XCTAssertEqual(tuple.2, [1, 1, 2, 3, 5]) + XCTAssertEqual(tuple.3, ["two", "strings"]) + } } From 5c29e8ecc979a0e631aa41b0d34f8860a7631422 Mon Sep 17 00:00:00 2001 From: Zev Eisenberg Date: Mon, 13 Mar 2017 11:23:14 -0400 Subject: [PATCH 05/14] Add missing syntax highlighting. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 00741ab..f3e6b90 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ You can find these behaviors in the [Promises+Extras.swift](https://github.com/k 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. -``` +```swift Promise(queue: .main, work: { fulfill, reject in viewController.present(viewControllerToPresent, animated: flag, completion: { fulfill() @@ -195,7 +195,7 @@ Because `ExecutionContext` is a protocol, other things can be passed here. One p To use this with table cells, the queue should be invalidated and reset on `prepareForReuse()`. -``` +```swift class SomeTableViewCell: UITableViewCell { var invalidatableQueue = InvalidatableQueue() From 337c639f38c550a7409c1ce8c306fb54443cca6d Mon Sep 17 00:00:00 2001 From: Andrew Kharchyshyn Date: Thu, 16 Mar 2017 16:33:35 +0200 Subject: [PATCH 06/14] Deployment target 8.0 --- Promise.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Promise.xcodeproj/project.pbxproj b/Promise.xcodeproj/project.pbxproj index d2bfe3a..b20b93d 100644 --- a/Promise.xcodeproj/project.pbxproj +++ b/Promise.xcodeproj/project.pbxproj @@ -315,7 +315,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -360,7 +360,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; From 01de1fb1ccddac2062aa591fa18785c38fdc632e Mon Sep 17 00:00:00 2001 From: John Wells Date: Thu, 18 May 2017 15:01:30 -0700 Subject: [PATCH 07/14] Add macOS target --- Promise.xcodeproj/project.pbxproj | 104 ++++++++++++++++++++++++++++++ Promise/Promise.h | 8 ++- 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/Promise.xcodeproj/project.pbxproj b/Promise.xcodeproj/project.pbxproj index b20b93d..25ac4d3 100644 --- a/Promise.xcodeproj/project.pbxproj +++ b/Promise.xcodeproj/project.pbxproj @@ -23,6 +23,9 @@ 3454F9D91D4FACD000985BBF /* Promise.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3454F9CE1D4FACD000985BBF /* Promise.framework */; }; 3454F9DE1D4FACD000985BBF /* PromiseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3454F9DD1D4FACD000985BBF /* PromiseTests.swift */; }; 3454F9E91D4FACFB00985BBF /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3454F9E81D4FACFB00985BBF /* Promise.swift */; }; + 52F3C1BE1ECE503D0028CA53 /* Promise+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE9F56E1D526F8A00185453 /* Promise+Extras.swift */; }; + 52F3C1BF1ECE503D0028CA53 /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3454F9E81D4FACFB00985BBF /* Promise.swift */; }; + 52F3C1C21ECE503D0028CA53 /* Promise.h in Headers */ = {isa = PBXBuildFile; fileRef = 3454F9D11D4FACD000985BBF /* Promise.h */; settings = {ATTRIBUTES = (Public, ); }; }; 72B61D3C1E00E5830008E829 /* Wrench.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72B61D3B1E00E5830008E829 /* Wrench.swift */; }; /* End PBXBuildFile section */ @@ -56,6 +59,7 @@ 3454F9DD1D4FACD000985BBF /* PromiseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseTests.swift; sourceTree = ""; }; 3454F9DF1D4FACD000985BBF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3454F9E81D4FACFB00985BBF /* Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Promise.swift; sourceTree = ""; }; + 52F3C1C71ECE503D0028CA53 /* Promise.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Promise.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 72B61D3B1E00E5830008E829 /* Wrench.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wrench.swift; sourceTree = ""; }; 76E25E381D8483D0000A58DF /* Promise.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Promise.playground; sourceTree = ""; }; /* End PBXFileReference section */ @@ -76,6 +80,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 52F3C1C01ECE503D0028CA53 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -94,6 +105,7 @@ children = ( 3454F9CE1D4FACD000985BBF /* Promise.framework */, 3454F9D81D4FACD000985BBF /* PromiseTests.xctest */, + 52F3C1C71ECE503D0028CA53 /* Promise.framework */, ); name = Products; sourceTree = ""; @@ -141,6 +153,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 52F3C1C11ECE503D0028CA53 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 52F3C1C21ECE503D0028CA53 /* Promise.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -180,6 +200,24 @@ productReference = 3454F9D81D4FACD000985BBF /* PromiseTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 52F3C1BC1ECE503D0028CA53 /* Promise Mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = 52F3C1C41ECE503D0028CA53 /* Build configuration list for PBXNativeTarget "Promise Mac" */; + buildPhases = ( + 52F3C1BD1ECE503D0028CA53 /* Sources */, + 52F3C1C01ECE503D0028CA53 /* Frameworks */, + 52F3C1C11ECE503D0028CA53 /* Headers */, + 52F3C1C31ECE503D0028CA53 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Promise Mac"; + productName = Promise; + productReference = 52F3C1C71ECE503D0028CA53 /* Promise.framework */; + productType = "com.apple.product-type.framework"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -212,6 +250,7 @@ projectRoot = ""; targets = ( 3454F9CD1D4FACD000985BBF /* Promise */, + 52F3C1BC1ECE503D0028CA53 /* Promise Mac */, 3454F9D71D4FACD000985BBF /* PromiseTests */, ); }; @@ -232,6 +271,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 52F3C1C31ECE503D0028CA53 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -264,6 +310,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 52F3C1BD1ECE503D0028CA53 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 52F3C1BE1ECE503D0028CA53 /* Promise+Extras.swift in Sources */, + 52F3C1BF1ECE503D0028CA53 /* Promise.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -431,6 +486,46 @@ }; name = Release; }; + 52F3C1C51ECE503D0028CA53 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Promise/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.khanlou.Promise; + PRODUCT_NAME = Promise; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 52F3C1C61ECE503D0028CA53 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Promise/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.khanlou.Promise; + PRODUCT_NAME = Promise; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -461,6 +556,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 52F3C1C41ECE503D0028CA53 /* Build configuration list for PBXNativeTarget "Promise Mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 52F3C1C51ECE503D0028CA53 /* Debug */, + 52F3C1C61ECE503D0028CA53 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 3454F9C51D4FACD000985BBF /* Project object */; diff --git a/Promise/Promise.h b/Promise/Promise.h index 7e94013..8263981 100644 --- a/Promise/Promise.h +++ b/Promise/Promise.h @@ -6,7 +6,13 @@ // // -#import +#include + +#if TARGET_OS_IPHONE +@import UIKit; +#else +@import AppKit; +#endif //! Project version number for Promise. FOUNDATION_EXPORT double PromiseVersionNumber; From cfee0130ba78f63deb36c124756e5356533bee00 Mon Sep 17 00:00:00 2001 From: John Wells Date: Thu, 18 May 2017 15:06:37 -0700 Subject: [PATCH 08/14] Make macOS target shared --- .../xcschemes/Promise macOS.xcscheme | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Promise.xcodeproj/xcshareddata/xcschemes/Promise macOS.xcscheme diff --git a/Promise.xcodeproj/xcshareddata/xcschemes/Promise macOS.xcscheme b/Promise.xcodeproj/xcshareddata/xcschemes/Promise macOS.xcscheme new file mode 100644 index 0000000..aa3bffa --- /dev/null +++ b/Promise.xcodeproj/xcshareddata/xcschemes/Promise macOS.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From b6533a3bb76f7eb49cfc7558c2bf4f6d01a7d912 Mon Sep 17 00:00:00 2001 From: Soroush Khanlou Date: Fri, 25 Aug 2017 08:57:50 -0400 Subject: [PATCH 09/14] fix typo in code --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f3e6b90..38aca7d 100644 --- a/README.md +++ b/README.md @@ -209,8 +209,8 @@ class SomeTableViewCell: UITableViewCell { override func prepareForReuse() { super.prepareForReuse() - token.invalidate() - token = InvalidationToken() + invalidatableQueue.invalidate() + invalidatableQueue = InvalidatableQueue() } } From 92bf8f0e53792effca92fd8b8216a279ef3b2d1d Mon Sep 17 00:00:00 2001 From: Soroush Khanlou Date: Sun, 17 Sep 2017 11:19:59 -0400 Subject: [PATCH 10/14] simplify the promise.all generics --- Promise/Promise+Extras.swift | 4 ++-- PromiseTests/PromiseAllTests.swift | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Promise/Promise+Extras.swift b/Promise/Promise+Extras.swift index 51f4a67..e8c9118 100644 --- a/Promise/Promise+Extras.swift +++ b/Promise/Promise+Extras.swift @@ -13,8 +13,8 @@ struct PromiseCheckError: Error { } extension Promise { /// Wait for all the promises you give it to fulfill, and once they have, fulfill itself /// with the array of all fulfilled values. - public static func all(_ promises: [Promise]) -> Promise<[T]> { - return Promise<[T]>(work: { fulfill, reject in + public static func all(_ promises: [Promise]) -> Promise<[Value]> { + return Promise<[Value]>(work: { fulfill, reject in guard !promises.isEmpty else { fulfill([]); return } for promise in promises { promise.then({ value in diff --git a/PromiseTests/PromiseAllTests.swift b/PromiseTests/PromiseAllTests.swift index 6a11b9e..129d184 100644 --- a/PromiseTests/PromiseAllTests.swift +++ b/PromiseTests/PromiseAllTests.swift @@ -30,7 +30,7 @@ class PromiseAllTests: XCTestCase { } }) - let final = Promise.all([promise1, promise2, promise3, promise4]) + let final = Promise.all([promise1, promise2, promise3, promise4]) final.then({ _ in expectation?.fulfill() @@ -49,7 +49,7 @@ class PromiseAllTests: XCTestCase { let promise2 = Promise(value: 2) let promise3 = Promise(value: 3) - let final = Promise.all([promise1, promise2, promise3]) + let final = Promise.all([promise1, promise2, promise3]) final.then({ _ in expectation?.fulfill() @@ -65,7 +65,7 @@ class PromiseAllTests: XCTestCase { weak var expectation = self.expectation(description: "`Promise.all` should wait until multiple promises are fulfilled before returning.") - let final: Promise<[Int]> = Promise.all([]) + let final: Promise<[Int]> = Promise.all([]) final.always({ expectation?.fulfill() }) @@ -89,7 +89,7 @@ class PromiseAllTests: XCTestCase { } }) - let final = Promise.all([promise1, promise2]) + let final = Promise.all([promise1, promise2]) final.then({ _ in XCTFail() @@ -126,7 +126,7 @@ class PromiseAllTests: XCTestCase { } }) - let final = Promise.all([promise1, promise2]) + let final = Promise.all([promise1, promise2]) final.then({ _ in XCTFail() From c91c7858203968c79f7dd341ea5f837344a5decf Mon Sep 17 00:00:00 2001 From: Soroush Khanlou Date: Sun, 17 Sep 2017 11:28:20 -0400 Subject: [PATCH 11/14] move all static functions to the Promises namespace --- Promise/Promise+Extras.swift | 83 +++++++++++++--------------- PromiseTests/PromiseAllTests.swift | 10 ++-- PromiseTests/PromiseDelayTests.swift | 8 +-- PromiseTests/PromiseRaceTests.swift | 14 ++--- PromiseTests/PromiseRetryTests.swift | 6 +- 5 files changed, 58 insertions(+), 63 deletions(-) diff --git a/Promise/Promise+Extras.swift b/Promise/Promise+Extras.swift index e8c9118..eed47cd 100644 --- a/Promise/Promise+Extras.swift +++ b/Promise/Promise+Extras.swift @@ -10,11 +10,11 @@ import Foundation struct PromiseCheckError: Error { } -extension Promise { +public enum Promises { /// Wait for all the promises you give it to fulfill, and once they have, fulfill itself /// with the array of all fulfilled values. - public static func all(_ promises: [Promise]) -> Promise<[Value]> { - return Promise<[Value]>(work: { fulfill, reject in + public static func all(_ promises: [Promise]) -> Promise<[T]> { + return Promise<[T]>(work: { fulfill, reject in guard !promises.isEmpty else { fulfill([]); return } for promise in promises { promise.then({ value in @@ -57,40 +57,6 @@ extension Promise { } }) } - public func addTimeout(_ timeout: TimeInterval) -> Promise { - return Promise.race(Array([self, Promise.timeout(timeout)])) - } - - @discardableResult - public func always(on queue: ExecutionContext = DispatchQueue.main, _ onComplete: @escaping () -> ()) -> Promise { - return then(on: queue, { _ in - onComplete() - }, { _ in - onComplete() - }) - } - - public func recover(_ recovery: @escaping (Error) throws -> Promise) -> Promise { - return Promise(work: { fulfill, reject in - self.then(fulfill).catch({ error in - do { - try recovery(error).then(fulfill, reject) - } catch (let error) { - reject(error) - } - }) - }) - } - - public func ensure(_ check: @escaping (Value) -> Bool) -> Promise { - return self.then({ (value: Value) -> Value in - guard check(value) else { - throw PromiseCheckError() - } - return value - }) - } - public static func retry(count: Int, delay: TimeInterval, generate: @escaping () -> Promise) -> Promise { if count <= 0 { @@ -105,13 +71,6 @@ extension Promise { }) } - @available(*, deprecated, message: "Use Promises.zip instead") - public static func zip(_ first: Promise, and second: Promise) -> Promise<(T, U)> { - return Promises.zip(first, second) - } -} - -public enum Promises { public static func zip(_ first: Promise, _ second: Promise) -> Promise<(T, U)> { return Promise<(T, U)>(work: { fulfill, reject in let resolver: (Any) -> () = { _ in @@ -160,3 +119,39 @@ public enum Promises { }) } } + +extension Promise { + public func addTimeout(_ timeout: TimeInterval) -> Promise { + return Promises.race(Array([self, Promises.timeout(timeout)])) + } + + @discardableResult + public func always(on queue: ExecutionContext = DispatchQueue.main, _ onComplete: @escaping () -> ()) -> Promise { + return then(on: queue, { _ in + onComplete() + }, { _ in + onComplete() + }) + } + + public func recover(_ recovery: @escaping (Error) throws -> Promise) -> Promise { + return Promise(work: { fulfill, reject in + self.then(fulfill).catch({ error in + do { + try recovery(error).then(fulfill, reject) + } catch (let error) { + reject(error) + } + }) + }) + } + + public func ensure(_ check: @escaping (Value) -> Bool) -> Promise { + return self.then({ (value: Value) -> Value in + guard check(value) else { + throw PromiseCheckError() + } + return value + }) + } +} diff --git a/PromiseTests/PromiseAllTests.swift b/PromiseTests/PromiseAllTests.swift index 129d184..0692ce1 100644 --- a/PromiseTests/PromiseAllTests.swift +++ b/PromiseTests/PromiseAllTests.swift @@ -30,7 +30,7 @@ class PromiseAllTests: XCTestCase { } }) - let final = Promise.all([promise1, promise2, promise3, promise4]) + let final = Promises.all([promise1, promise2, promise3, promise4]) final.then({ _ in expectation?.fulfill() @@ -49,7 +49,7 @@ class PromiseAllTests: XCTestCase { let promise2 = Promise(value: 2) let promise3 = Promise(value: 3) - let final = Promise.all([promise1, promise2, promise3]) + let final = Promises.all([promise1, promise2, promise3]) final.then({ _ in expectation?.fulfill() @@ -65,7 +65,7 @@ class PromiseAllTests: XCTestCase { weak var expectation = self.expectation(description: "`Promise.all` should wait until multiple promises are fulfilled before returning.") - let final: Promise<[Int]> = Promise.all([]) + let final: Promise<[Int]> = Promises.all([]) final.always({ expectation?.fulfill() }) @@ -89,7 +89,7 @@ class PromiseAllTests: XCTestCase { } }) - let final = Promise.all([promise1, promise2]) + let final = Promises.all([promise1, promise2]) final.then({ _ in XCTFail() @@ -126,7 +126,7 @@ class PromiseAllTests: XCTestCase { } }) - let final = Promise.all([promise1, promise2]) + let final = Promises.all([promise1, promise2]) final.then({ _ in XCTFail() diff --git a/PromiseTests/PromiseDelayTests.swift b/PromiseTests/PromiseDelayTests.swift index 757add3..7ccd7a9 100644 --- a/PromiseTests/PromiseDelayTests.swift +++ b/PromiseTests/PromiseDelayTests.swift @@ -13,8 +13,8 @@ class PromiseDelayTests: XCTestCase { func testDelay() { weak var expectation = self.expectation(description: "`Promise.delay` should succeed after the given time period has elapsed.") - let goodPromise = Promise<()>.delay(0.2) - let badPromise = Promise<()>.delay(1.1) + let goodPromise = Promises.delay(0.2) + let badPromise = Promises.delay(1.1) XCTAssert(goodPromise.isPending) XCTAssert(badPromise.isPending) @@ -30,8 +30,8 @@ class PromiseDelayTests: XCTestCase { func testTimeoutPromise() { weak var expectation = self.expectation(description: "`Promise.timeout` should succeed after the given time period has elapsed.") - let goodPromise: Promise<()> = Promise<()>.timeout(0.2) - let badPromise: Promise<()> = Promise<()>.timeout(1.1) + let goodPromise: Promise<()> = Promises.timeout(0.2) + let badPromise: Promise<()> = Promises.timeout(1.1) XCTAssert(goodPromise.isPending) XCTAssert(badPromise.isPending) diff --git a/PromiseTests/PromiseRaceTests.swift b/PromiseTests/PromiseRaceTests.swift index 01b98e4..773555d 100644 --- a/PromiseTests/PromiseRaceTests.swift +++ b/PromiseTests/PromiseRaceTests.swift @@ -30,7 +30,7 @@ class PromiseRaceTests: XCTestCase { } }) - let final = Promise.race([promise1, promise2, promise3]) + let final = Promises.race([promise1, promise2, promise3]) final.then({ _ in expectation?.fulfill() @@ -50,9 +50,9 @@ class PromiseRaceTests: XCTestCase { reject(SimpleError()) } }) - let promise2 = Promise<()>.delay(0.1).then({ 2 }) + let promise2 = Promises.delay(0.1).then({ 2 }) - let final = Promise.race([promise1, promise2]) + let final = Promises.race([promise1, promise2]) final.catch({ _ in expectation?.fulfill() @@ -66,9 +66,9 @@ class PromiseRaceTests: XCTestCase { weak var expectation = self.expectation(description: "`Promise.race` should reject as soon as the first promise is reject.") let promise1 = Promise(value: 1) - let promise2 = Promise<()>.delay(0.1).then({ 5 }) + let promise2 = Promises.delay(0.1).then({ 5 }) - let final = Promise.race([promise1, promise2]) + let final = Promises.race([promise1, promise2]) final.then({ _ in expectation?.fulfill() @@ -83,9 +83,9 @@ class PromiseRaceTests: XCTestCase { weak var expectation = self.expectation(description: "`Promise.race` should reject as soon as the first promise is reject.") let promise1 = Promise(error: SimpleError()) - let promise2 = Promise<()>.delay(0.1).then({ 5 }) + let promise2 = Promises.delay(0.1).then({ 5 }) - let final = Promise.race([promise1, promise2]) + let final = Promises.race([promise1, promise2]) final.catch({ _ in expectation?.fulfill() diff --git a/PromiseTests/PromiseRetryTests.swift b/PromiseTests/PromiseRetryTests.swift index 1c25199..9d446c3 100644 --- a/PromiseTests/PromiseRetryTests.swift +++ b/PromiseTests/PromiseRetryTests.swift @@ -14,7 +14,7 @@ class PromiseRetryTests: XCTestCase { weak var expectation = self.expectation(description: "`Promise.retry` should retry and eventually succeed.") var currentCount = 3 - let promise = Promise.retry(count: 3, delay: 0, generate: { () -> Promise in + let promise = Promises.retry(count: 3, delay: 0, generate: { () -> Promise in if currentCount == 1 { return Promise(value: 8) } @@ -33,7 +33,7 @@ class PromiseRetryTests: XCTestCase { weak var expectation = self.expectation(description: "`Promise.retry` should never retry if it succeeds immediately.") var currentCount = 1 - let promise = Promise.retry(count: 3, delay: 0, generate: { () -> Promise in + let promise = Promises.retry(count: 3, delay: 0, generate: { () -> Promise in if currentCount == 0 { XCTFail() } @@ -51,7 +51,7 @@ class PromiseRetryTests: XCTestCase { func testRetryWithNeverSuccess() { weak var expectation = self.expectation(description: "`Promise.retry` should never retry if it succeeds immediately.") - let promise = Promise.retry(count: 3, delay: 0, generate: { () -> Promise in + let promise = Promises.retry(count: 3, delay: 0, generate: { () -> Promise in return Promise(error: SimpleError()) }).always({ expectation?.fulfill() From ad7a85d84fe632e0f197656719e82e35ea293e35 Mon Sep 17 00:00:00 2001 From: Soroush Khanlou Date: Sun, 17 Sep 2017 11:35:34 -0400 Subject: [PATCH 12/14] add promises.kickoff --- Promise.xcodeproj/project.pbxproj | 4 +++ Promise/Promise+Extras.swift | 12 +++++++ PromiseTests/PromiseKickoffTests.swift | 45 ++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 PromiseTests/PromiseKickoffTests.swift diff --git a/Promise.xcodeproj/project.pbxproj b/Promise.xcodeproj/project.pbxproj index 25ac4d3..1d24f5c 100644 --- a/Promise.xcodeproj/project.pbxproj +++ b/Promise.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 1A2A8CFA1D5D717D00421E3E /* PromiseAlwaysTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2A8CF91D5D717D00421E3E /* PromiseAlwaysTests.swift */; }; 1A2A8CFC1D5D743100421E3E /* PromiseRecoverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2A8CFB1D5D743100421E3E /* PromiseRecoverTests.swift */; }; 1A2A8CFE1D5D764600421E3E /* PromiseRetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2A8CFD1D5D764600421E3E /* PromiseRetryTests.swift */; }; + 1A42330C1F6ECC250045B066 /* PromiseKickoffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A42330B1F6ECC250045B066 /* PromiseKickoffTests.swift */; }; 1A7CC22F1DF4D37400929E7C /* PromiseEnsureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7CC22E1DF4D37400929E7C /* PromiseEnsureTests.swift */; }; 1AE9F56F1D526F8A00185453 /* Promise+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE9F56E1D526F8A00185453 /* Promise+Extras.swift */; }; 1AF54F3B1D67D60000557CCB /* PromiseZipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AF54F3A1D67D60000557CCB /* PromiseZipTests.swift */; }; @@ -46,6 +47,7 @@ 1A2A8CF91D5D717D00421E3E /* PromiseAlwaysTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseAlwaysTests.swift; sourceTree = ""; }; 1A2A8CFB1D5D743100421E3E /* PromiseRecoverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseRecoverTests.swift; sourceTree = ""; }; 1A2A8CFD1D5D764600421E3E /* PromiseRetryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseRetryTests.swift; sourceTree = ""; }; + 1A42330B1F6ECC250045B066 /* PromiseKickoffTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseKickoffTests.swift; sourceTree = ""; }; 1A7CC22E1DF4D37400929E7C /* PromiseEnsureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseEnsureTests.swift; sourceTree = ""; }; 1AE9F56E1D526F8A00185453 /* Promise+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Promise+Extras.swift"; sourceTree = ""; }; 1AF54F3A1D67D60000557CCB /* PromiseZipTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseZipTests.swift; sourceTree = ""; }; @@ -138,6 +140,7 @@ 1A7CC22E1DF4D37400929E7C /* PromiseEnsureTests.swift */, 72B61D3B1E00E5830008E829 /* Wrench.swift */, 1A0073671E2865CC00EB319A /* ExecutionContextTests.swift */, + 1A42330B1F6ECC250045B066 /* PromiseKickoffTests.swift */, ); path = PromiseTests; sourceTree = ""; @@ -305,6 +308,7 @@ 1AFF80FA1D5166C000C55D5A /* delay.swift in Sources */, 1AFF80FC1D51676700C55D5A /* PromiseAllTests.swift in Sources */, 3454F9DE1D4FACD000985BBF /* PromiseTests.swift in Sources */, + 1A42330C1F6ECC250045B066 /* PromiseKickoffTests.swift in Sources */, 1A2A8CF61D5D36C500421E3E /* PromiseDelayTests.swift in Sources */, 1A7CC22F1DF4D37400929E7C /* PromiseEnsureTests.swift in Sources */, ); diff --git a/Promise/Promise+Extras.swift b/Promise/Promise+Extras.swift index eed47cd..ea5d247 100644 --- a/Promise/Promise+Extras.swift +++ b/Promise/Promise+Extras.swift @@ -71,6 +71,18 @@ public enum Promises { }) } +// public static func kickoff(_ block: @escaping () throws -> Promise) -> Promise { +// return Promise(value: ()).then(block) +// } + + public static func kickoff(_ block: @escaping () throws -> T) -> Promise { + do { + return try Promise(value: block()) + } catch { + return Promise(error: error) + } + } + public static func zip(_ first: Promise, _ second: Promise) -> Promise<(T, U)> { return Promise<(T, U)>(work: { fulfill, reject in let resolver: (Any) -> () = { _ in diff --git a/PromiseTests/PromiseKickoffTests.swift b/PromiseTests/PromiseKickoffTests.swift new file mode 100644 index 0000000..eb36b45 --- /dev/null +++ b/PromiseTests/PromiseKickoffTests.swift @@ -0,0 +1,45 @@ +// +// PromiseKickoffTests.swift +// Promise +// +// Created by Soroush Khanlou on 9/17/17. +// +// + +import XCTest +@testable import Promise + +class PromiseKickoffTests: XCTestCase { + + func testKickoff() { + weak var expectation = self.expectation(description: "Kicking off should result in a valid value.") + + let promise = Promises + .kickoff({ + return "kicked off!" + }) + .always({ + expectation?.fulfill() + }) + + waitForExpectations(timeout: 1, handler: nil) + XCTAssert(promise.value == "kicked off!") + + } + + func testFailingKickoff() { + weak var expectation = self.expectation(description: "Kicking off should result in a valid value.") + + let promise = Promises + .kickoff({ + throw SimpleError() + }) + .always({ + expectation?.fulfill() + }) + + waitForExpectations(timeout: 1, handler: nil) + XCTAssert(promise.isRejected) + } + +} From 46d2bb305317e6a9033aaef65588114894de958e Mon Sep 17 00:00:00 2001 From: Soroush Khanlou Date: Sun, 17 Sep 2017 11:46:59 -0400 Subject: [PATCH 13/14] update readme --- README.md | 55 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 38aca7d..2f210e3 100644 --- a/README.md +++ b/README.md @@ -139,15 +139,36 @@ fetchUsers() ``` 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. + +```swift +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` -`Promise.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 `Promise.all` make that super easy: +`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: ```swift let userPromises = users.map({ user in APIClient.followUser(user) }) -Promise<()>.all(userPromises) +Promises.all(userPromises) .then({ //all the users are now followed! }) @@ -155,23 +176,27 @@ Promise<()>.all(userPromises) //one of the API requests failed }) ``` -### `ensure` -`ensure` is a method that takes a predicate, and rejects the promise chain if that predicate fails. +### `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. ```swift -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 - }) +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. + }) ``` -### Others + +This (coupled with `Optional.unrwap()`) 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. From d214c69f9e0fc5bc780fc086bd0575140808ceaf Mon Sep 17 00:00:00 2001 From: Soroush Khanlou Date: Sun, 17 Sep 2017 11:48:41 -0400 Subject: [PATCH 14/14] fix spacing in readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 2f210e3..477ccd2 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ 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. @@ -44,6 +45,7 @@ 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. @@ -62,6 +64,7 @@ fetchUsers() self.followers = followers }) ``` + To catch any errors that are created along the way, you can add a `catch` block as well: ```swift @@ -79,6 +82,7 @@ fetchUsers() 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 @@ -137,6 +141,7 @@ fetchUsers() 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` @@ -273,6 +278,7 @@ URLSession.shared.dataTask(with: request) // use the json }) ``` + Working with optionals can be made simpler with a little extension. ```swift @@ -287,6 +293,7 @@ extension Optional { } } ``` + 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: ```swift @@ -294,6 +301,7 @@ Because you're in an environment where you can freely throw and it will be handl return try (json["user"] as? [String: Any]).unwrap() }) ``` + And you will transform your optional into a non-optional. ### Threading Model