-
-
Notifications
You must be signed in to change notification settings - Fork 52
Experimental async/await support #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
e47d8c0
wip
kateinoigakukun 0dd6d35
Add sample
kateinoigakukun 50fdb85
Remove prototype
kateinoigakukun afb9d04
Apply relative pointer patch for wasm
kateinoigakukun 9d7cc89
Update example code to test without capturing context
kateinoigakukun ad41808
Remove prototype target
kateinoigakukun ed6ff5d
WIP
kateinoigakukun a52035e
Merge branch 'katei/simplify-jspromise' into katei/async-promise
kateinoigakukun 4c39af2
Update JSPromise interface
kateinoigakukun 1c43175
Add cxxLanguageStandard option
kateinoigakukun 00677af
Strip unused headers
kateinoigakukun 0883e71
Update toolchain version
kateinoigakukun 15a90c3
Exclude non-source files
kateinoigakukun 9ec3904
Fix example project build
kateinoigakukun 9c97219
Update copy-header to download from remote source
kateinoigakukun 6326fcf
Merge remote-tracking branch 'origin/main' into katei/async-promise
kateinoigakukun d7db074
Adopt JSPromise changes
kateinoigakukun a839e2c
Split Xcode support code
kateinoigakukun 9afcad7
Add API documents
kateinoigakukun ee97fbd
Adopt JSPromsie API change part2
kateinoigakukun df235c8
Add linguist-vendored
kateinoigakukun a2bdc7f
Update README
kateinoigakukun 366d74e
Revert JSObject changes
kateinoigakukun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Sources/_CJavaScriptEventLoop/swift/* linguist-vendored | ||
Sources/_CJavaScriptEventLoop/llvm/* linguist-vendored |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
wasm-5.3.0-RELEASE | ||
wasm-DEVELOPMENT-SNAPSHOT-2021-01-10-a |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
IntegrationTests/TestSuites/Sources/ConcurrencyTests/UnitTestUtils.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import JavaScriptKit | ||
|
||
var printTestNames = false | ||
// Uncomment the next line to print the name of each test suite before running it. | ||
// This will make it easier to debug any errors that occur on the JS side. | ||
//printTestNames = true | ||
|
||
func test(_ name: String, testBlock: () throws -> Void) throws { | ||
if printTestNames { print(name) } | ||
do { | ||
try testBlock() | ||
} catch { | ||
print("Error in \(name)") | ||
print(error) | ||
throw error | ||
} | ||
} | ||
|
||
func asyncTest(_ name: String, testBlock: () async throws -> Void) async throws -> Void { | ||
if printTestNames { print(name) } | ||
do { | ||
await try testBlock() | ||
} catch { | ||
print("Error in \(name)") | ||
print(error) | ||
throw error | ||
} | ||
} | ||
|
||
struct MessageError: Error { | ||
let message: String | ||
let file: StaticString | ||
let line: UInt | ||
let column: UInt | ||
init(_ message: String, file: StaticString, line: UInt, column: UInt) { | ||
self.message = message | ||
self.file = file | ||
self.line = line | ||
self.column = column | ||
} | ||
} | ||
|
||
func expectEqual<T: Equatable>( | ||
_ lhs: T, _ rhs: T, | ||
file: StaticString = #file, line: UInt = #line, column: UInt = #column | ||
) throws { | ||
if lhs != rhs { | ||
throw MessageError("Expect to be equal \"\(lhs)\" and \"\(rhs)\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectCast<T, U>( | ||
_ value: T, to type: U.Type = U.self, | ||
file: StaticString = #file, line: UInt = #line, column: UInt = #column | ||
) throws -> U { | ||
guard let value = value as? U else { | ||
throw MessageError("Expect \"\(value)\" to be \(U.self)", file: file, line: line, column: column) | ||
} | ||
return value | ||
} | ||
|
||
func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObject { | ||
switch value { | ||
case let .object(ref): return ref | ||
default: | ||
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectArray(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSArray { | ||
guard let array = value.array else { | ||
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column) | ||
} | ||
return array | ||
} | ||
|
||
func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunction { | ||
switch value { | ||
case let .function(ref): return ref | ||
default: | ||
throw MessageError("Type of \(value) should be \"function\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectBoolean(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Bool { | ||
switch value { | ||
case let .boolean(bool): return bool | ||
default: | ||
throw MessageError("Type of \(value) should be \"boolean\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectNumber(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Double { | ||
switch value { | ||
case let .number(number): return number | ||
default: | ||
throw MessageError("Type of \(value) should be \"number\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectString(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> String { | ||
switch value { | ||
case let .string(string): return String(string) | ||
default: | ||
throw MessageError("Type of \(value) should be \"string\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectAsyncThrow<T>(_ body: @autoclosure () async throws -> T, file: StaticString = #file, line: UInt = #line, column: UInt = #column) async throws -> Error { | ||
do { | ||
_ = await try body() | ||
} catch { | ||
return error | ||
} | ||
throw MessageError("Expect to throw an exception", file: file, line: line, column: column) | ||
} | ||
|
||
func expectNotNil<T>(_ value: T?, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws { | ||
switch value { | ||
case .some: return | ||
case .none: | ||
throw MessageError("Expect a non-nil value", file: file, line: line, column: column) | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
IntegrationTests/TestSuites/Sources/ConcurrencyTests/main.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import JavaScriptEventLoop | ||
import JavaScriptKit | ||
|
||
JavaScriptEventLoop.install() | ||
|
||
try JavaScriptEventLoop.runAsync { | ||
struct E: Error, Equatable { | ||
let value: Int | ||
} | ||
|
||
await try asyncTest("Task.runDetached value") { | ||
let handle = Task.runDetached { 1 } | ||
await try expectEqual(handle.get(), 1) | ||
} | ||
|
||
await try asyncTest("Task.runDetached throws") { | ||
let handle = Task.runDetached { | ||
throw E(value: 2) | ||
} | ||
let error = await try expectAsyncThrow(await handle.get()) | ||
let e = try expectCast(error, to: E.self) | ||
try expectEqual(e, E(value: 2)) | ||
} | ||
|
||
await try asyncTest("await resolved Promise") { | ||
let p = JSPromise(resolver: { resolve in | ||
resolve(.success(1)) | ||
}) | ||
await try expectEqual(p.await(), 1) | ||
} | ||
|
||
await try asyncTest("await rejected Promise") { | ||
let p = JSPromise(resolver: { resolve in | ||
resolve(.failure(.number(3))) | ||
}) | ||
let error = await try expectAsyncThrow(await p.await()) | ||
let jsValue = try expectCast(error, to: JSValue.self) | ||
try expectEqual(jsValue, 3) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
const { startWasiTask } = require("../lib"); | ||
|
||
global.fetch = require('node-fetch'); | ||
global.sleep = function () { | ||
return new Promise(resolve => { | ||
setTimeout(() => { | ||
resolve('resolved'); | ||
}, 2000); | ||
}); | ||
} | ||
|
||
startWasiTask("./dist/ConcurrencyTests.wasm").catch((err) => { | ||
console.log(err); | ||
process.exit(1); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,41 @@ | ||
// swift-tools-version:5.2 | ||
// swift-tools-version:5.3 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "JavaScriptKit", | ||
products: [ | ||
.library(name: "JavaScriptKit", targets: ["JavaScriptKit"]), | ||
.library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]), | ||
], | ||
targets: [ | ||
.target( | ||
name: "JavaScriptKit", | ||
dependencies: ["_CJavaScriptKit"] | ||
), | ||
.target( | ||
name: "JavaScriptEventLoop", | ||
dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"], | ||
swiftSettings: [ | ||
.unsafeFlags(["-Xfrontend", "-enable-experimental-concurrency"]), | ||
] | ||
), | ||
.target(name: "_CJavaScriptKit"), | ||
] | ||
.target( | ||
name: "_CJavaScriptEventLoop", | ||
dependencies: ["_CJavaScriptKit"], | ||
exclude: [ | ||
"README", "LICENSE-llvm", "LICENSE-swift", "scripts", | ||
"include/swift/ABI/MetadataKind.def", | ||
"include/swift/ABI/ValueWitness.def", | ||
"include/swift/AST/ReferenceStorage.def", | ||
"include/swift/Demangling/DemangleNodes.def", | ||
"include/swift/Demangling/ValueWitnessMangling.def", | ||
], | ||
linkerSettings: [ | ||
.linkedLibrary("swift_Concurrency", .when(platforms: [.wasi])), | ||
] | ||
), | ||
], | ||
cxxLanguageStandard: .cxx14 | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import JavaScriptKit | ||
|
||
/// A `JSFunction` wrapper that enables async-function calls. | ||
/// Exceptions produced by JavaScript functions will be thrown as `JSValue`. | ||
/// | ||
/// ```swift | ||
/// let fetch = JSObject.global.fetch.function!.async | ||
/// let result = await try! fetch("https://api.github.com/zen") | ||
/// ``` | ||
public class JSAsyncFunction { | ||
private let base: JSFunction | ||
public init(_ base: JSFunction) { | ||
self.base = base | ||
} | ||
|
||
/// Call this function with given `arguments` and binding given `this` as context. | ||
/// - Parameters: | ||
/// - this: The value to be passed as the `this` parameter to this function. | ||
/// - arguments: Arguments to be passed to this function. | ||
/// - Returns: The result of this call. | ||
@discardableResult | ||
public func callAsFunction(this: JSObject? = nil, arguments: [ConvertibleToJSValue]) async throws -> JSValue { | ||
let result = base.callAsFunction(this: this, arguments: arguments) | ||
guard let object = result.object, let promise = JSPromise(object) else { | ||
fatalError("'\(result)' should be Promise object") | ||
} | ||
return await try promise.await() | ||
} | ||
|
||
/// A variadic arguments version of `callAsFunction`. | ||
@discardableResult | ||
public func callAsFunction(this: JSObject? = nil, _ arguments: ConvertibleToJSValue...) async throws -> JSValue { | ||
await try callAsFunction(this: this, arguments: arguments) | ||
} | ||
} | ||
|
||
public extension JSFunction { | ||
/// A modifier to call this function as a async function | ||
/// | ||
/// ```swift | ||
/// let fetch = JSObject.global.fetch.function!.async | ||
/// let result = await try! fetch("https://api.github.com/zen") | ||
/// ``` | ||
var `async`: JSAsyncFunction { | ||
JSAsyncFunction(self) | ||
} | ||
} | ||
|
||
/// A `JSObject` wrapper that enables async method calls capturing `this`. | ||
/// Exceptions produced by JavaScript functions will be thrown as `JSValue`. | ||
@dynamicMemberLookup | ||
public class JSAsyncingObject { | ||
private let base: JSObject | ||
public init(_ base: JSObject) { | ||
self.base = base | ||
} | ||
|
||
/// Returns the `name` member method binding this object as `this` context. | ||
/// - Parameter name: The name of this object's member to access. | ||
/// - Returns: The `name` member method binding this object as `this` context. | ||
public subscript(_ name: String) -> ((ConvertibleToJSValue...) async throws -> JSValue)? { | ||
guard let function = base[name].function?.async else { return nil } | ||
return { [base] (arguments: ConvertibleToJSValue...) in | ||
await try function(this: base, arguments: arguments) | ||
} | ||
} | ||
|
||
/// A convenience method of `subscript(_ name: String) -> ((ConvertibleToJSValue...) throws -> JSValue)?` | ||
/// to access the member through Dynamic Member Lookup. | ||
public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) async throws -> JSValue)? { | ||
self[name] | ||
} | ||
} | ||
|
||
|
||
public extension JSObject { | ||
|
||
/// A modifier to call methods as async methods capturing `this` | ||
/// | ||
/// ```swift | ||
/// let fetch = JSObject.global.fetch.function!.async | ||
/// let result = await try! fetch("https://api.github.com/zen") | ||
/// ``` | ||
var asyncing: JSAsyncingObject { | ||
JSAsyncingObject(self) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.