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