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