1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 /**
4  Represents a concurrent function that can only be called asynchronously, thus its JavaScript equivalent returns a Promise.
5  As opposed to `AsyncFunctionComponent`, it can leverage the new Swift's concurrency model and take the async/await closure.
6  */
7 public final class ConcurrentFunctionDefinition<Args, FirstArgType, ReturnType>: AnyFunction {
8   typealias ClosureType = (Args) async throws -> ReturnType
9 
10   let body: ClosureType
11 
12   init(
13     _ name: String,
14     firstArgType: FirstArgType.Type,
15     dynamicArgumentTypes: [AnyDynamicType],
16     _ body: @escaping ClosureType
17   ) {
18     self.name = name
19     self.body = body
20     self.dynamicArgumentTypes = dynamicArgumentTypes
21   }
22 
23   // MARK: - AnyFunction
24 
25   let name: String
26 
27   let dynamicArgumentTypes: [AnyDynamicType]
28 
29   var takesOwner: Bool = false
30 
31   func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext, callback: @escaping (FunctionCallResult) -> Void) {
32     var arguments: [Any]
33 
34     do {
35       try validateArgumentsNumber(function: self, received: args.count)
36 
37       arguments = concat(
38         arguments: args,
39         withOwner: owner,
40         withPromise: nil,
41         forFunction: self,
42         appContext: appContext
43       )
44 
45       // All `JavaScriptValue` args must be preliminarly converted on the JS thread, before we jump to the function's queue.
46       arguments = try cast(jsValues: args, forFunction: self, appContext: appContext)
47     } catch let error as Exception {
48       callback(.failure(error))
49       return
50     } catch {
51       callback(.failure(UnexpectedException(error)))
52       return
53     }
54 
55     // Switch from the synchronous context to asynchronous
56     Task { [arguments] in
57       let result: Result<Any, Exception>
58 
59       do {
60         // Convert arguments to the types desired by the function.
61         let finalArguments = try cast(arguments: arguments, forFunction: self, appContext: appContext)
62 
63         // TODO: Right now we force cast the tuple in all types of functions, but we should throw another exception here.
64         // swiftlint:disable force_cast
65         let argumentsTuple = try Conversions.toTuple(finalArguments) as! Args
66         let returnValue = try await body(argumentsTuple)
67 
68         result = .success(returnValue)
69       } catch let error as Exception {
70         result = .failure(FunctionCallException(name).causedBy(error))
71       } catch {
72         result = .failure(UnexpectedException(error))
73       }
74 
75       callback(result)
76     }
77   }
78 
79   // MARK: - JavaScriptObjectBuilder
80 
buildnull81   func build(appContext: AppContext) throws -> JavaScriptObject {
82     return try appContext.runtime.createAsyncFunction(name, argsCount: argumentsCount) {
83       [weak appContext, weak self, name] this, args, resolve, reject in
84 
85       guard let appContext else {
86         let exception = Exceptions.AppContextLost()
87         return reject(exception.code, exception.description, nil)
88       }
89       guard let self else {
90         let exception = NativeFunctionUnavailableException(name)
91         return reject(exception.code, exception.description, nil)
92       }
93       self.call(by: this, withArguments: args, appContext: appContext) { result in
94         switch result {
95           case .failure(let error):
96             reject(error.code, error.description, nil)
97           case .success(let value):
98             resolve(value)
99         }
100       }
101     }
102   }
103 }
104 
105 /**
106  Concurrently-executing asynchronous function without arguments.
107  */
108 public func AsyncFunction<R>(
109   _ name: String,
110   @_implicitSelfCapture _ closure: @escaping () async throws -> R
111 ) -> ConcurrentFunctionDefinition<(), Void, R> {
112   return ConcurrentFunctionDefinition(
113     name,
114     firstArgType: Void.self,
115     dynamicArgumentTypes: [],
116     closure
117   )
118 }
119 
120 /**
121  Concurrently-executing asynchronous function with one argument.
122  */
123 public func AsyncFunction<R, A0: AnyArgument>(
124   _ name: String,
125   @_implicitSelfCapture _ closure: @escaping (A0) async throws -> R
126 ) -> ConcurrentFunctionDefinition<(A0), A0, R> {
127   return ConcurrentFunctionDefinition(
128     name,
129     firstArgType: A0.self,
130     dynamicArgumentTypes: [~A0.self],
131     closure
132   )
133 }
134 
135 /**
136  Concurrently-executing asynchronous function with two arguments.
137  */
138 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument>(
139   _ name: String,
140   @_implicitSelfCapture _ closure: @escaping (A0, A1) async throws -> R
141 ) -> ConcurrentFunctionDefinition<(A0, A1), A0, R> {
142   return ConcurrentFunctionDefinition(
143     name,
144     firstArgType: A0.self,
145     dynamicArgumentTypes: [~A0.self, ~A1.self],
146     closure
147   )
148 }
149 
150 /**
151  Concurrently-executing asynchronous function with three arguments.
152  */
153 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument>(
154   _ name: String,
155   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2) async throws -> R
156 ) -> ConcurrentFunctionDefinition<(A0, A1, A2), A0, R> {
157   return ConcurrentFunctionDefinition(
158     name,
159     firstArgType: A0.self,
160     dynamicArgumentTypes: [
161       ~A0.self,
162       ~A1.self,
163       ~A2.self
164     ],
165     closure
166   )
167 }
168 
169 /**
170  Concurrently-executing asynchronous function with four arguments.
171  */
172 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument>(
173   _ name: String,
174   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3) async throws -> R
175 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3), A0, R> {
176   return ConcurrentFunctionDefinition(
177     name,
178     firstArgType: A0.self,
179     dynamicArgumentTypes: [
180       ~A0.self,
181       ~A1.self,
182       ~A2.self,
183       ~A3.self
184     ],
185     closure
186   )
187 }
188 
189 /**
190  Concurrently-executing asynchronous function with five arguments.
191  */
192 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument>(
193   _ name: String,
194   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4) async throws -> R
195 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4), A0, R> {
196   return ConcurrentFunctionDefinition(
197     name,
198     firstArgType: A0.self,
199     dynamicArgumentTypes: [
200       ~A0.self,
201       ~A1.self,
202       ~A2.self,
203       ~A3.self,
204       ~A4.self
205     ],
206     closure
207   )
208 }
209 
210 /**
211  Concurrently-executing asynchronous function with six arguments.
212  */
213 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument>(
214   _ name: String,
215   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5) async throws -> R
216 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4, A5), A0, R> {
217   return ConcurrentFunctionDefinition(
218     name,
219     firstArgType: A0.self,
220     dynamicArgumentTypes: [
221       ~A0.self,
222       ~A1.self,
223       ~A2.self,
224       ~A3.self,
225       ~A4.self,
226       ~A5.self
227     ],
228     closure
229   )
230 }
231 
232 /**
233  Concurrently-executing asynchronous function with seven arguments.
234  */
235 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument>(
236   _ name: String,
237   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6) async throws -> R
238 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4, A5, A6), A0, R> {
239   return ConcurrentFunctionDefinition(
240     name,
241     firstArgType: A0.self,
242     dynamicArgumentTypes: [
243       ~A0.self,
244       ~A1.self,
245       ~A2.self,
246       ~A3.self,
247       ~A4.self,
248       ~A5.self,
249       ~A6.self
250     ],
251     closure
252   )
253 }
254 
255 /**
256  Concurrently-executing asynchronous function with eight arguments.
257  */
258 public func AsyncFunction<
259   R,
260   A0: AnyArgument,
261   A1: AnyArgument,
262   A2: AnyArgument,
263   A3: AnyArgument,
264   A4: AnyArgument,
265   A5: AnyArgument,
266   A6: AnyArgument,
267   A7: AnyArgument
268 >(
269   _ name: String,
270   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6, A7) async throws -> R
271 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4, A5, A6, A7), A0, R> {
272   return ConcurrentFunctionDefinition(
273     name,
274     firstArgType: A0.self,
275     dynamicArgumentTypes: [
276       ~A0.self,
277       ~A1.self,
278       ~A2.self,
279       ~A3.self,
280       ~A4.self,
281       ~A5.self,
282       ~A6.self,
283       ~A7.self
284     ],
285     closure
286   )
287 }
288