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     let arguments: [Any]
33 
34     do {
35       arguments = concat(
36         arguments: try cast(arguments: args, forFunction: self, appContext: appContext),
37         withOwner: owner,
38         forFunction: self,
39         appContext: appContext
40       )
41     } catch let error as Exception {
42       callback(.failure(error))
43       return
44     } catch {
45       callback(.failure(UnexpectedException(error)))
46       return
47     }
48 
49     // Switch from the synchronous context to asynchronous
50     Task { [arguments] in
51       let result: Result<Any, Exception>
52 
53       do {
54         // TODO: Right now we force cast the tuple in all types of functions, but we should throw another exception here.
55         // swiftlint:disable force_cast
56         let argumentsTuple = try Conversions.toTuple(arguments) as! Args
57         let returnValue = try await body(argumentsTuple)
58 
59         result = .success(returnValue)
60       } catch let error as Exception {
61         result = .failure(FunctionCallException(name).causedBy(error))
62       } catch {
63         result = .failure(UnexpectedException(error))
64       }
65 
66       callback(result)
67     }
68   }
69 
70   // MARK: - JavaScriptObjectBuilder
71 
72   func build(appContext: AppContext) throws -> JavaScriptObject {
73     return try appContext.runtime.createAsyncFunction(name, argsCount: argumentsCount) { [weak self, name] this, args, resolve, reject in
74       guard let self = self else {
75         let exception = NativeFunctionUnavailableException(name)
76         return reject(exception.code, exception.description, nil)
77       }
78       self.call(by: this, withArguments: args, appContext: appContext) { result in
79         switch result {
80         case .failure(let error):
81           reject(error.code, error.description, nil)
82         case .success(let value):
83           resolve(value)
84         }
85       }
86     }
87   }
88 }
89 
90 /**
91  Concurrently-executing asynchronous function without arguments.
92  */
93 public func AsyncFunction<R>(
94   _ name: String,
95   @_implicitSelfCapture _ closure: @escaping () async throws -> R
96 ) -> ConcurrentFunctionDefinition<(), Void, R> {
97   return ConcurrentFunctionDefinition(
98     name,
99     firstArgType: Void.self,
100     dynamicArgumentTypes: [],
101     closure
102   )
103 }
104 
105 /**
106  Concurrently-executing asynchronous function with one argument.
107  */
108 public func AsyncFunction<R, A0: AnyArgument>(
109   _ name: String,
110   @_implicitSelfCapture _ closure: @escaping (A0) async throws -> R
111 ) -> ConcurrentFunctionDefinition<(A0), A0, R> {
112   return ConcurrentFunctionDefinition(
113     name,
114     firstArgType: A0.self,
115     dynamicArgumentTypes: [~A0.self],
116     closure
117   )
118 }
119 
120 /**
121  Concurrently-executing asynchronous function with two arguments.
122  */
123 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument>(
124   _ name: String,
125   @_implicitSelfCapture _ closure: @escaping (A0, A1) async throws -> R
126 ) -> ConcurrentFunctionDefinition<(A0, A1), A0, R> {
127   return ConcurrentFunctionDefinition(
128     name,
129     firstArgType: A0.self,
130     dynamicArgumentTypes: [~A0.self, ~A1.self],
131     closure
132   )
133 }
134 
135 /**
136  Concurrently-executing asynchronous function with three arguments.
137  */
138 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument>(
139   _ name: String,
140   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2) async throws -> R
141 ) -> ConcurrentFunctionDefinition<(A0, A1, A2), A0, R> {
142   return ConcurrentFunctionDefinition(
143     name,
144     firstArgType: A0.self,
145     dynamicArgumentTypes: [
146       ~A0.self,
147       ~A1.self,
148       ~A2.self
149     ],
150     closure
151   )
152 }
153 
154 /**
155  Concurrently-executing asynchronous function with four arguments.
156  */
157 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument>(
158   _ name: String,
159   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3) async throws -> R
160 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3), A0, R> {
161   return ConcurrentFunctionDefinition(
162     name,
163     firstArgType: A0.self,
164     dynamicArgumentTypes: [
165       ~A0.self,
166       ~A1.self,
167       ~A2.self,
168       ~A3.self
169     ],
170     closure
171   )
172 }
173 
174 /**
175  Concurrently-executing asynchronous function with five arguments.
176  */
177 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument>(
178   _ name: String,
179   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4) async throws -> R
180 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4), A0, R> {
181   return ConcurrentFunctionDefinition(
182     name,
183     firstArgType: A0.self,
184     dynamicArgumentTypes: [
185       ~A0.self,
186       ~A1.self,
187       ~A2.self,
188       ~A3.self,
189       ~A4.self
190     ],
191     closure
192   )
193 }
194 
195 /**
196  Concurrently-executing asynchronous function with six arguments.
197  */
198 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument>(
199   _ name: String,
200   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5) async throws -> R
201 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4, A5), A0, R> {
202   return ConcurrentFunctionDefinition(
203     name,
204     firstArgType: A0.self,
205     dynamicArgumentTypes: [
206       ~A0.self,
207       ~A1.self,
208       ~A2.self,
209       ~A3.self,
210       ~A4.self,
211       ~A5.self
212     ],
213     closure
214   )
215 }
216 
217 /**
218  Concurrently-executing asynchronous function with seven arguments.
219  */
220 public func AsyncFunction<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument>(
221   _ name: String,
222   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6) async throws -> R
223 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4, A5, A6), A0, R> {
224   return ConcurrentFunctionDefinition(
225     name,
226     firstArgType: A0.self,
227     dynamicArgumentTypes: [
228       ~A0.self,
229       ~A1.self,
230       ~A2.self,
231       ~A3.self,
232       ~A4.self,
233       ~A5.self,
234       ~A6.self
235     ],
236     closure
237   )
238 }
239 
240 /**
241  Concurrently-executing asynchronous function with eight arguments.
242  */
243 public func AsyncFunction<
244   R,
245   A0: AnyArgument,
246   A1: AnyArgument,
247   A2: AnyArgument,
248   A3: AnyArgument,
249   A4: AnyArgument,
250   A5: AnyArgument,
251   A6: AnyArgument,
252   A7: AnyArgument
253 >(
254   _ name: String,
255   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6, A7) async throws -> R
256 ) -> ConcurrentFunctionDefinition<(A0, A1, A2, A3, A4, A5, A6, A7), A0, R> {
257   return ConcurrentFunctionDefinition(
258     name,
259     firstArgType: A0.self,
260     dynamicArgumentTypes: [
261       ~A0.self,
262       ~A1.self,
263       ~A2.self,
264       ~A3.self,
265       ~A4.self,
266       ~A5.self,
267       ~A6.self,
268       ~A7.self
269     ],
270     closure
271   )
272 }
273