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