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