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 */ 12 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.body = body 50 51 // Drop the last argument type if it's the `Promise`. 52 self.dynamicArgumentTypes = takesPromise ? dynamicArgumentTypes.dropLast(1) : dynamicArgumentTypes 53 } 54 55 // MARK: - AnyFunction 56 57 let name: String 58 59 let dynamicArgumentTypes: [AnyDynamicType] 60 61 var argumentsCount: Int { 62 return dynamicArgumentTypes.count - (takesOwner ? 1 : 0) 63 } 64 65 var takesOwner: Bool = false 66 67 func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext, callback: @escaping (FunctionCallResult) -> ()) { 68 let promise = Promise { value in 69 callback(.success(Conversions.convertFunctionResult(value))) 70 } rejecter: { exception in 71 callback(.failure(exception)) 72 } 73 var arguments: [Any] = [] 74 75 do { 76 arguments = concat( 77 arguments: try cast(arguments: args, forFunction: self, appContext: appContext), 78 withOwner: owner, 79 forFunction: self, 80 appContext: appContext 81 ) 82 } catch let error as Exception { 83 callback(.failure(error)) 84 return 85 } catch { 86 callback(.failure(UnexpectedException(error))) 87 return 88 } 89 90 // Add promise to the array of arguments if necessary. 91 if takesPromise { 92 arguments.append(promise) 93 } 94 95 let queue = queue ?? defaultQueue 96 97 queue.async { [body, name] in 98 let returnedValue: ReturnType? 99 100 do { 101 let argumentsTuple = try Conversions.toTuple(arguments) as! Args 102 returnedValue = try body(argumentsTuple) 103 } catch let error as Exception { 104 promise.reject(FunctionCallException(name).causedBy(error)) 105 return 106 } catch { 107 promise.reject(UnexpectedException(error)) 108 return 109 } 110 if !self.takesPromise { 111 promise.resolve(returnedValue) 112 } 113 } 114 } 115 116 // MARK: - JavaScriptObjectBuilder 117 118 func build(appContext: AppContext) throws -> JavaScriptObject { 119 return try appContext.runtime.createAsyncFunction(name, argsCount: argumentsCount) { [weak self, name] this, args, resolve, reject in 120 guard let self = self else { 121 let exception = NativeFunctionUnavailableException(name) 122 return reject(exception.code, exception.description, nil) 123 } 124 self.call(by: this, withArguments: args, appContext: appContext) { result in 125 switch result { 126 case .failure(let error): 127 reject(error.code, error.description, nil) 128 case .success(let value): 129 resolve(value) 130 } 131 } 132 } 133 } 134 135 // MARK: - AnyAsyncFunctionComponent 136 137 public func runOnQueue(_ queue: DispatchQueue?) -> Self { 138 self.queue = queue 139 return self 140 } 141 } 142 143 // MARK: - Exceptions 144 145 internal final class NativeFunctionUnavailableException: GenericException<String> { 146 override var reason: String { 147 return "Native function '\(param)' is no longer available in memory" 148 } 149 } 150 151 // MARK: - Factories 152 153 /** 154 Asynchronous function without arguments. 155 */ 156 public func AsyncFunction<R>( 157 _ name: String, 158 @_implicitSelfCapture _ closure: @escaping () throws -> R 159 ) -> AsyncFunctionComponent<(), Void, R> { 160 return AsyncFunctionComponent( 161 name, 162 firstArgType: Void.self, 163 dynamicArgumentTypes: [], 164 closure 165 ) 166 } 167 168 /** 169 Asynchronous function with one argument. 170 */ 171 public func AsyncFunction<R, A0: AnyArgument>( 172 _ name: String, 173 @_implicitSelfCapture _ closure: @escaping (A0) throws -> R 174 ) -> AsyncFunctionComponent<(A0), A0, R> { 175 return AsyncFunctionComponent( 176 name, 177 firstArgType: A0.self, 178 dynamicArgumentTypes: [~A0.self], 179 closure 180 ) 181 } 182 183 /** 184 Asynchronous function with two arguments. 185 */ 186 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument>( 187 _ name: String, 188 @_implicitSelfCapture _ closure: @escaping (A0, A1) throws -> R 189 ) -> AsyncFunctionComponent<(A0, A1), A0, R> { 190 return AsyncFunctionComponent( 191 name, 192 firstArgType: A0.self, 193 dynamicArgumentTypes: [~A0.self, ~A1.self], 194 closure 195 ) 196 } 197 198 /** 199 Asynchronous function with three arguments. 200 */ 201 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument>( 202 _ name: String, 203 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2) throws -> R 204 ) -> AsyncFunctionComponent<(A0, A1, A2), A0, R> { 205 return AsyncFunctionComponent( 206 name, 207 firstArgType: A0.self, 208 dynamicArgumentTypes: [ 209 ~A0.self, 210 ~A1.self, 211 ~A2.self 212 ], 213 closure 214 ) 215 } 216 217 /** 218 Asynchronous function with four arguments. 219 */ 220 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument>( 221 _ name: String, 222 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3) throws -> R 223 ) -> AsyncFunctionComponent<(A0, A1, A2, A3), A0, R> { 224 return AsyncFunctionComponent( 225 name, 226 firstArgType: A0.self, 227 dynamicArgumentTypes: [ 228 ~A0.self, 229 ~A1.self, 230 ~A2.self, 231 ~A3.self 232 ], 233 closure 234 ) 235 } 236 237 /** 238 Asynchronous function with five arguments. 239 */ 240 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument>( 241 _ name: String, 242 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4) throws -> R 243 ) -> AsyncFunctionComponent<(A0, A1, A2, A3, A4), A0, R> { 244 return AsyncFunctionComponent( 245 name, 246 firstArgType: A0.self, 247 dynamicArgumentTypes: [ 248 ~A0.self, 249 ~A1.self, 250 ~A2.self, 251 ~A3.self, 252 ~A4.self 253 ], 254 closure 255 ) 256 } 257 258 /** 259 Asynchronous function with six arguments. 260 */ 261 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument>( 262 _ name: String, 263 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5) throws -> R 264 ) -> AsyncFunctionComponent<(A0, A1, A2, A3, A4, A5), A0, R> { 265 return AsyncFunctionComponent( 266 name, 267 firstArgType: A0.self, 268 dynamicArgumentTypes: [ 269 ~A0.self, 270 ~A1.self, 271 ~A2.self, 272 ~A3.self, 273 ~A4.self, 274 ~A5.self 275 ], 276 closure 277 ) 278 } 279 280 /** 281 Asynchronous function with seven arguments. 282 */ 283 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument>( 284 _ name: String, 285 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6) throws -> R 286 ) -> AsyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6), A0, R> { 287 return AsyncFunctionComponent( 288 name, 289 firstArgType: A0.self, 290 dynamicArgumentTypes: [ 291 ~A0.self, 292 ~A1.self, 293 ~A2.self, 294 ~A3.self, 295 ~A4.self, 296 ~A5.self, 297 ~A6.self 298 ], 299 closure 300 ) 301 } 302 303 /** 304 Asynchronous function with eight arguments. 305 */ 306 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument, A7: AnyArgument>( 307 _ name: String, 308 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6, A7) throws -> R 309 ) -> AsyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6, A7), A0, R> { 310 return AsyncFunctionComponent( 311 name, 312 firstArgType: A0.self, 313 dynamicArgumentTypes: [ 314 ~A0.self, 315 ~A1.self, 316 ~A2.self, 317 ~A3.self, 318 ~A4.self, 319 ~A5.self, 320 ~A6.self, 321 ~A7.self 322 ], 323 closure 324 ) 325 } 326