1 // Copyright 2022-present 650 Industries. All rights reserved. 2 3 /** 4 Represents a concurrent function that can only be called asynchronously, thus its JavaScript equivalent returns a Promise. 5 As opposed to `AsyncFunctionComponent`, it can leverage the new Swift's concurrency model and take the async/await closure. 6 */ 7 public final class ConcurrentFunctionDefinition<Args, FirstArgType, ReturnType>: AnyFunction { 8 typealias ClosureType = (Args) async throws -> ReturnType 9 10 let body: ClosureType 11 12 init( 13 _ name: String, 14 firstArgType: FirstArgType.Type, 15 dynamicArgumentTypes: [AnyDynamicType], 16 _ body: @escaping ClosureType 17 ) { 18 self.name = name 19 self.body = body 20 self.dynamicArgumentTypes = dynamicArgumentTypes 21 } 22 23 // MARK: - AnyFunction 24 25 let name: String 26 27 let dynamicArgumentTypes: [AnyDynamicType] 28 29 var takesOwner: Bool = false 30 31 func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext, callback: @escaping (FunctionCallResult) -> Void) { 32 let arguments: [Any] 33 34 do { 35 arguments = concat( 36 arguments: try cast(arguments: args, forFunction: self, appContext: appContext), 37 withOwner: owner, 38 forFunction: self, 39 appContext: appContext 40 ) 41 } catch let error as Exception { 42 callback(.failure(error)) 43 return 44 } catch { 45 callback(.failure(UnexpectedException(error))) 46 return 47 } 48 49 // Switch from the synchronous context to asynchronous 50 Task { [arguments] in 51 let result: Result<Any, Exception> 52 53 do { 54 // TODO: Right now we force cast the tuple in all types of functions, but we should throw another exception here. 55 // swiftlint:disable force_cast 56 let argumentsTuple = try Conversions.toTuple(arguments) as! Args 57 let returnValue = try await body(argumentsTuple) 58 59 result = .success(returnValue) 60 } catch let error as Exception { 61 result = .failure(FunctionCallException(name).causedBy(error)) 62 } catch { 63 result = .failure(UnexpectedException(error)) 64 } 65 66 callback(result) 67 } 68 } 69 70 // MARK: - JavaScriptObjectBuilder 71 72 func build(appContext: AppContext) throws -> JavaScriptObject { 73 return try appContext.runtime.createAsyncFunction(name, argsCount: argumentsCount) { [weak self, name] this, args, resolve, reject in 74 guard let self = self else { 75 let exception = NativeFunctionUnavailableException(name) 76 return reject(exception.code, exception.description, nil) 77 } 78 self.call(by: this, withArguments: args, appContext: appContext) { result in 79 switch result { 80 case .failure(let error): 81 reject(error.code, error.description, nil) 82 case .success(let value): 83 resolve(value) 84 } 85 } 86 } 87 } 88 } 89 90 /** 91 Concurrently-executing asynchronous function without arguments. 92 */ 93 public func AsyncFunction<R>( 94 _ name: String, 95 @_implicitSelfCapture _ closure: @escaping () async throws -> R 96 ) -> ConcurrentFunctionDefinition<(), Void, R> { 97 return ConcurrentFunctionDefinition( 98 name, 99 firstArgType: Void.self, 100 dynamicArgumentTypes: [], 101 closure 102 ) 103 } 104 105 /** 106 Concurrently-executing asynchronous function with one argument. 107 */ 108 public func AsyncFunction<R, A0: AnyArgument>( 109 _ name: String, 110 @_implicitSelfCapture _ closure: @escaping (A0) async throws -> R 111 ) -> ConcurrentFunctionDefinition<(A0), A0, R> { 112 return ConcurrentFunctionDefinition( 113 name, 114 firstArgType: A0.self, 115 dynamicArgumentTypes: [~A0.self], 116 closure 117 ) 118 } 119 120 /** 121 Concurrently-executing asynchronous function with two arguments. 122 */ 123 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument>( 124 _ name: String, 125 @_implicitSelfCapture _ closure: @escaping (A0, A1) async throws -> R 126 ) -> ConcurrentFunctionDefinition<(A0, A1), A0, R> { 127 return ConcurrentFunctionDefinition( 128 name, 129 firstArgType: A0.self, 130 dynamicArgumentTypes: [~A0.self, ~A1.self], 131 closure 132 ) 133 } 134 135 /** 136 Concurrently-executing asynchronous function with three arguments. 137 */ 138 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument>( 139 _ name: String, 140 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2) async throws -> R 141 ) -> ConcurrentFunctionDefinition<(A0, A1, A2), A0, R> { 142 return ConcurrentFunctionDefinition( 143 name, 144 firstArgType: A0.self, 145 dynamicArgumentTypes: [ 146 ~A0.self, 147 ~A1.self, 148 ~A2.self 149 ], 150 closure 151 ) 152 } 153 154 /** 155 Concurrently-executing asynchronous function with four arguments. 156 */ 157 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument>( 158 _ name: String, 159 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3) async throws -> R 160 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3), A0, R> { 161 return ConcurrentFunctionDefinition( 162 name, 163 firstArgType: A0.self, 164 dynamicArgumentTypes: [ 165 ~A0.self, 166 ~A1.self, 167 ~A2.self, 168 ~A3.self 169 ], 170 closure 171 ) 172 } 173 174 /** 175 Concurrently-executing asynchronous function with five arguments. 176 */ 177 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument>( 178 _ name: String, 179 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4) async throws -> R 180 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4), A0, R> { 181 return ConcurrentFunctionDefinition( 182 name, 183 firstArgType: A0.self, 184 dynamicArgumentTypes: [ 185 ~A0.self, 186 ~A1.self, 187 ~A2.self, 188 ~A3.self, 189 ~A4.self 190 ], 191 closure 192 ) 193 } 194 195 /** 196 Concurrently-executing asynchronous function with six arguments. 197 */ 198 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument>( 199 _ name: String, 200 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5) async throws -> R 201 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4, A5), A0, R> { 202 return ConcurrentFunctionDefinition( 203 name, 204 firstArgType: A0.self, 205 dynamicArgumentTypes: [ 206 ~A0.self, 207 ~A1.self, 208 ~A2.self, 209 ~A3.self, 210 ~A4.self, 211 ~A5.self 212 ], 213 closure 214 ) 215 } 216 217 /** 218 Concurrently-executing asynchronous function with seven arguments. 219 */ 220 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument>( 221 _ name: String, 222 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6) async throws -> R 223 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4, A5, A6), A0, R> { 224 return ConcurrentFunctionDefinition( 225 name, 226 firstArgType: A0.self, 227 dynamicArgumentTypes: [ 228 ~A0.self, 229 ~A1.self, 230 ~A2.self, 231 ~A3.self, 232 ~A4.self, 233 ~A5.self, 234 ~A6.self 235 ], 236 closure 237 ) 238 } 239 240 /** 241 Concurrently-executing asynchronous function with eight arguments. 242 */ 243 public func AsyncFunction< 244 R, 245 A0: AnyArgument, 246 A1: AnyArgument, 247 A2: AnyArgument, 248 A3: AnyArgument, 249 A4: AnyArgument, 250 A5: AnyArgument, 251 A6: AnyArgument, 252 A7: AnyArgument 253 >( 254 _ name: String, 255 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6, A7) async throws -> R 256 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4, A5, A6, A7), A0, R> { 257 return ConcurrentFunctionDefinition( 258 name, 259 firstArgType: A0.self, 260 dynamicArgumentTypes: [ 261 ~A0.self, 262 ~A1.self, 263 ~A2.self, 264 ~A3.self, 265 ~A4.self, 266 ~A5.self, 267 ~A6.self, 268 ~A7.self 269 ], 270 closure 271 ) 272 } 273