1 // Copyright 2022-present 650 Industries. All rights reserved. 2 3 import Dispatch 4 5 /** 6 Type-erased protocol for asynchronous functions. 7 */ 8 internal protocol AnyAsyncFunctionComponent: AnyFunction { 9 /** 10 Specifies on which queue the function should run. 11 */ runOnQueuenull12 func runOnQueue(_ queue: DispatchQueue?) -> Self 13 } 14 15 /** 16 The default queue used for module's async function calls. 17 */ 18 private let defaultQueue = DispatchQueue(label: "expo.modules.AsyncFunctionQueue", qos: .userInitiated) 19 20 /** 21 Represents a function that can only be called asynchronously, thus its JavaScript equivalent returns a Promise. 22 */ 23 public final class AsyncFunctionComponent<Args, FirstArgType, ReturnType>: AnyAsyncFunctionComponent { 24 typealias ClosureType = (Args) throws -> ReturnType 25 26 /** 27 The underlying closure to run when the function is called. 28 */ 29 let body: ClosureType 30 31 /** 32 Bool value indicating whether the function takes promise as the last argument. 33 */ 34 let takesPromise: Bool 35 36 /** 37 Dispatch queue on which each function's call is run. 38 */ 39 var queue: DispatchQueue? 40 41 init( 42 _ name: String, 43 firstArgType: FirstArgType.Type, 44 dynamicArgumentTypes: [AnyDynamicType], 45 _ body: @escaping ClosureType 46 ) { 47 self.name = name 48 self.takesPromise = dynamicArgumentTypes.last?.wraps(Promise.self) ?? false 49 self.dynamicArgumentTypes = dynamicArgumentTypes 50 self.body = body 51 } 52 53 // MARK: - AnyFunction 54 55 let name: String 56 57 let dynamicArgumentTypes: [AnyDynamicType] 58 59 var argumentsCount: Int { 60 return dynamicArgumentTypes.count - (takesOwner ? 1 : 0) - (takesPromise ? 1 : 0) 61 } 62 63 var takesOwner: Bool = false 64 65 func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext, callback: @escaping (FunctionCallResult) -> ()) { 66 let promise = Promise { value in 67 callback(.success(Conversions.convertFunctionResult(value))) 68 } rejecter: { exception in 69 callback(.failure(exception)) 70 } 71 var arguments: [Any] = concat( 72 arguments: args, 73 withOwner: owner, 74 withPromise: takesPromise ? promise : nil, 75 forFunction: self, 76 appContext: appContext 77 ) 78 79 do { 80 try validateArgumentsNumber(function: self, received: args.count) 81 82 // All `JavaScriptValue` args must be preliminarly converted on the JS thread, so before we jump to the function's queue. 83 arguments = try cast(jsValues: arguments, forFunction: self, appContext: appContext) 84 } catch let error as Exception { 85 callback(.failure(error)) 86 return 87 } catch { 88 callback(.failure(UnexpectedException(error))) 89 return 90 } 91 92 let queue = queue ?? defaultQueue 93 94 queue.async { [body, name] in 95 let returnedValue: ReturnType? 96 97 do { 98 // Convert arguments to the types desired by the function. 99 arguments = try cast(arguments: arguments, forFunction: self, appContext: appContext) 100 101 // swiftlint:disable:next force_cast 102 let argumentsTuple = try Conversions.toTuple(arguments) as! Args 103 104 returnedValue = try body(argumentsTuple) 105 } catch let error as Exception { 106 promise.reject(FunctionCallException(name).causedBy(error)) 107 return 108 } catch { 109 promise.reject(UnexpectedException(error)) 110 return 111 } 112 if !self.takesPromise { 113 promise.resolve(returnedValue) 114 } 115 } 116 } 117 118 // MARK: - JavaScriptObjectBuilder 119 120 func build(appContext: AppContext) throws -> JavaScriptObject { 121 return try appContext.runtime.createAsyncFunction(name, argsCount: argumentsCount) { [weak self, name] this, args, resolve, reject in 122 guard let self = self else { 123 let exception = NativeFunctionUnavailableException(name) 124 return reject(exception.code, exception.description, nil) 125 } 126 self.call(by: this, withArguments: args, appContext: appContext) { result in 127 switch result { 128 case .failure(let error): 129 reject(error.code, error.description, nil) 130 case .success(let value): 131 resolve(value) 132 } 133 } 134 } 135 } 136 137 // MARK: - AnyAsyncFunctionComponent 138 139 public func runOnQueue(_ queue: DispatchQueue?) -> Self { 140 self.queue = queue 141 return self 142 } 143 } 144 145 // MARK: - Exceptions 146 147 internal final class NativeFunctionUnavailableException: GenericException<String> { 148 override var reason: String { 149 return "Native function '\(param)' is no longer available in memory" 150 } 151 } 152 153 // MARK: - Factories 154 155 /** 156 Asynchronous function without arguments. 157 */ 158 public func AsyncFunction<R>( 159 _ name: String, 160 @_implicitSelfCapture _ closure: @escaping () throws -> R 161 ) -> AsyncFunctionComponent<(), Void, R> { 162 return AsyncFunctionComponent( 163 name, 164 firstArgType: Void.self, 165 dynamicArgumentTypes: [], 166 closure 167 ) 168 } 169 170 /** 171 Asynchronous function with one argument. 172 */ 173 public func AsyncFunction<R, A0: AnyArgument>( 174 _ name: String, 175 @_implicitSelfCapture _ closure: @escaping (A0) throws -> R 176 ) -> AsyncFunctionComponent<(A0), A0, R> { 177 return AsyncFunctionComponent( 178 name, 179 firstArgType: A0.self, 180 dynamicArgumentTypes: [~A0.self], 181 closure 182 ) 183 } 184 185 /** 186 Asynchronous function with two arguments. 187 */ 188 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument>( 189 _ name: String, 190 @_implicitSelfCapture _ closure: @escaping (A0, A1) throws -> R 191 ) -> AsyncFunctionComponent<(A0, A1), A0, R> { 192 return AsyncFunctionComponent( 193 name, 194 firstArgType: A0.self, 195 dynamicArgumentTypes: [~A0.self, ~A1.self], 196 closure 197 ) 198 } 199 200 /** 201 Asynchronous function with three arguments. 202 */ 203 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument>( 204 _ name: String, 205 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2) throws -> R 206 ) -> AsyncFunctionComponent<(A0, A1, A2), A0, R> { 207 return AsyncFunctionComponent( 208 name, 209 firstArgType: A0.self, 210 dynamicArgumentTypes: [ 211 ~A0.self, 212 ~A1.self, 213 ~A2.self 214 ], 215 closure 216 ) 217 } 218 219 /** 220 Asynchronous function with four arguments. 221 */ 222 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument>( 223 _ name: String, 224 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3) throws -> R 225 ) -> AsyncFunctionComponent<(A0, A1, A2, A3), A0, R> { 226 return AsyncFunctionComponent( 227 name, 228 firstArgType: A0.self, 229 dynamicArgumentTypes: [ 230 ~A0.self, 231 ~A1.self, 232 ~A2.self, 233 ~A3.self 234 ], 235 closure 236 ) 237 } 238 239 /** 240 Asynchronous function with five arguments. 241 */ 242 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument>( 243 _ name: String, 244 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4) throws -> R 245 ) -> AsyncFunctionComponent<(A0, A1, A2, A3, A4), A0, R> { 246 return AsyncFunctionComponent( 247 name, 248 firstArgType: A0.self, 249 dynamicArgumentTypes: [ 250 ~A0.self, 251 ~A1.self, 252 ~A2.self, 253 ~A3.self, 254 ~A4.self 255 ], 256 closure 257 ) 258 } 259 260 /** 261 Asynchronous function with six arguments. 262 */ 263 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument>( 264 _ name: String, 265 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5) throws -> R 266 ) -> AsyncFunctionComponent<(A0, A1, A2, A3, A4, A5), A0, R> { 267 return AsyncFunctionComponent( 268 name, 269 firstArgType: A0.self, 270 dynamicArgumentTypes: [ 271 ~A0.self, 272 ~A1.self, 273 ~A2.self, 274 ~A3.self, 275 ~A4.self, 276 ~A5.self 277 ], 278 closure 279 ) 280 } 281 282 /** 283 Asynchronous function with seven arguments. 284 */ 285 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument>( 286 _ name: String, 287 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6) throws -> R 288 ) -> AsyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6), A0, R> { 289 return AsyncFunctionComponent( 290 name, 291 firstArgType: A0.self, 292 dynamicArgumentTypes: [ 293 ~A0.self, 294 ~A1.self, 295 ~A2.self, 296 ~A3.self, 297 ~A4.self, 298 ~A5.self, 299 ~A6.self 300 ], 301 closure 302 ) 303 } 304 305 /** 306 Asynchronous function with eight arguments. 307 */ 308 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument, A7: AnyArgument>( 309 _ name: String, 310 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6, A7) throws -> R 311 ) -> AsyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6, A7), A0, R> { 312 return AsyncFunctionComponent( 313 name, 314 firstArgType: A0.self, 315 dynamicArgumentTypes: [ 316 ~A0.self, 317 ~A1.self, 318 ~A2.self, 319 ~A3.self, 320 ~A4.self, 321 ~A5.self, 322 ~A6.self, 323 ~A7.self 324 ], 325 closure 326 ) 327 } 328