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