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