diff --git a/tests/server/html.js b/examples/server/html.js
similarity index 100%
rename from tests/server/html.js
rename to examples/server/html.js
diff --git a/tests/server/meow.txt b/examples/server/meow.txt
similarity index 100%
rename from tests/server/meow.txt
rename to examples/server/meow.txt
diff --git a/tests/server/radix3.js b/examples/server/radix3.js
similarity index 100%
rename from tests/server/radix3.js
rename to examples/server/radix3.js
diff --git a/tests/server/server.js b/examples/server/server.js
similarity index 100%
rename from tests/server/server.js
rename to examples/server/server.js
diff --git a/include/ant.h b/include/ant.h
index 679cb05..1af877d 100644
--- a/include/ant.h
+++ b/include/ant.h
@@ -72,4 +72,5 @@ void js_prop_iter_end(js_prop_iter_t *iter);
jsval_t js_mkpromise(struct js *js);
void js_resolve_promise(struct js *js, jsval_t promise, jsval_t value);
-void js_reject_promise(struct js *js, jsval_t promise, jsval_t value);
\ No newline at end of file
+void js_reject_promise(struct js *js, jsval_t promise, jsval_t value);
+void js_run_event_loop(struct js *js);
\ No newline at end of file
diff --git a/meson.build b/meson.build
index 4216e40..ae0f933 100644
--- a/meson.build
+++ b/meson.build
@@ -41,7 +41,7 @@ endif
build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip()
version_conf = configuration_data()
-version_conf.set('ANT_VERSION', '0.0.6.6')
+version_conf.set('ANT_VERSION', '0.0.6.7')
version_conf.set('ANT_GIT_HASH', git_hash)
version_conf.set('ANT_BUILD_DATE', build_date)
diff --git a/src/ant.c b/src/ant.c
index bcd5e88..9bb06e9 100644
--- a/src/ant.c
+++ b/src/ant.c
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
@@ -345,6 +346,23 @@ static bool has_pending_coroutines(void) {
return pending_coroutines.head != NULL;
}
+void js_run_event_loop(struct js *js) {
+ while (has_pending_microtasks() || has_pending_timers()) {
+ process_microtasks(js);
+
+ if (has_pending_timers()) {
+ int64_t next_timeout_ms = get_next_timer_timeout();
+
+ if (next_timeout_ms <= 0) {
+ process_timers(js);
+ continue;
+ } else {
+ usleep(next_timeout_ms > 1000000 ? 1000000 : next_timeout_ms * 1000);
+ }
+ }
+ }
+}
+
static void free_coroutine(coroutine_t *coro) {
if (coro) {
if (coro->args) free(coro->args);
@@ -2839,36 +2857,71 @@ static jsval_t js_unary(struct js *js) {
builtin_promise_then(js, then_args, 2);
js->this_val = saved_this;
+ uint8_t saved_flags = js->flags;
+ const char *saved_code = js->code;
+ jsoff_t saved_clen = js->clen;
+ jsoff_t saved_pos = js->pos;
+ uint8_t saved_tok = js->tok;
+ uint8_t saved_consumed = js->consumed;
+
while (!coro->is_settled) {
+ if (has_pending_timers()) {
+ int64_t next_timeout = get_next_timer_timeout();
+ if (next_timeout <= 0) {
+ process_timers(js);
+ js->flags = saved_flags;
+ js->code = saved_code;
+ js->clen = saved_clen;
+ js->pos = saved_pos;
+ js->tok = saved_tok;
+ js->consumed = saved_consumed;
+ }
+ }
+
process_microtasks(js);
+ js->flags = saved_flags;
+ js->code = saved_code;
+ js->clen = saved_clen;
+ js->pos = saved_pos;
+ js->tok = saved_tok;
+ js->consumed = saved_consumed;
+
+ int coroutines_checked = 0;
+ int total_coroutines = 0;
- while (has_pending_coroutines()) {
+ coroutine_t *temp = pending_coroutines.head;
+ while (temp) {
+ total_coroutines++;
+ temp = temp->next;
+ }
+
+ while (has_pending_coroutines() && coroutines_checked < total_coroutines) {
coroutine_t *resumed = dequeue_coroutine();
+ coroutines_checked++;
+
if (resumed == coro) {
jsval_t result = resumed->result;
bool is_error = resumed->is_error;
free_coroutine(resumed);
- if (is_error) {
- return js_throw(js, result);
- }
+ if (is_error) return js_throw(js, result);
return result;
}
enqueue_coroutine(resumed);
}
- if (!has_pending_microtasks() && has_pending_timers()) {
- int64_t next_timeout = get_next_timer_timeout();
- if (next_timeout <= 0) {
- process_timers(js);
- continue;
- }
- }
-
if (!has_pending_microtasks() && !has_pending_coroutines() && !has_pending_timers()) {
free_coroutine(coro);
return js_mkerr(js, "await: promise never settled");
}
+
+ if (has_pending_timers() && !has_pending_microtasks() && !has_pending_coroutines()) {
+ int64_t next_timeout = get_next_timer_timeout();
+ if (next_timeout > 0) {
+ int64_t sleep_ms = next_timeout < 1 ? 1 : (next_timeout > 1 ? 1 : next_timeout);
+ usleep(sleep_ms * 1000);
+ }
+ }
}
jsval_t result = coro->result;
diff --git a/src/main.c b/src/main.c
index d92ac18..c63f8b2 100644
--- a/src/main.c
+++ b/src/main.c
@@ -135,21 +135,7 @@ int main(int argc, char *argv[]) {
int result = execute_module(js, module_file);
- while (has_pending_microtasks() || has_pending_timers()) {
- process_microtasks(js);
-
- if (has_pending_timers()) {
- int64_t next_timeout_ms = get_next_timer_timeout();
-
- if (next_timeout_ms <= 0) {
- process_timers(js);
- continue;
- } else {
- usleep(next_timeout_ms > 1000000 ? 1000000 : next_timeout_ms * 1000);
- }
- }
- }
-
+ js_run_event_loop(js);
if (dump) js_dump(js);
js_destroy(js);
diff --git a/tests/simple_await_test.cjs b/tests/simple_await_test.cjs
new file mode 100644
index 0000000..1db3ae7
--- /dev/null
+++ b/tests/simple_await_test.cjs
@@ -0,0 +1,5 @@
+async function test() {
+ await new Promise(resolve => setTimeout(resolve, 10));
+ return "done";
+}
+test().then(r => console.log(r));
diff --git a/tests/simple_sync_await.cjs b/tests/simple_sync_await.cjs
new file mode 100644
index 0000000..75b7dae
--- /dev/null
+++ b/tests/simple_sync_await.cjs
@@ -0,0 +1,9 @@
+// Test that works (sync promise)
+async function testSync() {
+ console.log("Before await");
+ const result = await Promise.resolve("SYNC");
+ console.log("After await:", result);
+ return "RETURNED_" + result;
+}
+
+testSync().then(r => console.log("Final:", r));
diff --git a/tests/simple_timer_await.cjs b/tests/simple_timer_await.cjs
new file mode 100644
index 0000000..a55fccf
--- /dev/null
+++ b/tests/simple_timer_await.cjs
@@ -0,0 +1,16 @@
+async function test() {
+ console.log("1: Before await");
+ const x = await new Promise(resolve => setTimeout(() => {
+ console.log("2: Timer fired");
+ resolve("VALUE");
+ }, 50));
+ console.log("3: After await, x =", x);
+ console.log("4: About to return");
+ return "FINAL_" + x;
+}
+
+console.log("A: Calling test()");
+const p = test();
+console.log("B: test() returned");
+p.then(result => console.log("C: Promise resolved:", result));
+console.log("D: Script end");
diff --git a/tests/test_await_return.cjs b/tests/test_await_return.cjs
new file mode 100644
index 0000000..c0d003e
--- /dev/null
+++ b/tests/test_await_return.cjs
@@ -0,0 +1,7 @@
+async function test() {
+ console.log("Before");
+ await new Promise(resolve => setTimeout(resolve, 10));
+ return "done";
+}
+
+test().then(r => console.log("Result:", r));
diff --git a/tests/test_concurrent_timing.cjs b/tests/test_concurrent_timing.cjs
new file mode 100644
index 0000000..db93ab8
--- /dev/null
+++ b/tests/test_concurrent_timing.cjs
@@ -0,0 +1,20 @@
+console.log("START");
+const start = Date.now();
+
+async function delay(ms, name) {
+ console.log(`${name}: Starting ${ms}ms wait at +${Date.now() - start}ms`);
+ await new Promise(resolve => setTimeout(() => {
+ console.log(`${name}: Timer fired at +${Date.now() - start}ms`);
+ resolve();
+ }, ms));
+ console.log(`${name}: Resumed at +${Date.now() - start}ms`);
+ return `${name}_done`;
+}
+
+console.log("Calling func1 at +${Date.now() - start}ms");
+delay(100, "func1").then(r => console.log(`func1 complete: ${r} at +${Date.now() - start}ms`));
+console.log("Called func1, calling func2 at +${Date.now() - start}ms");
+delay(50, "func2").then(r => console.log(`func2 complete: ${r} at +${Date.now() - start}ms`));
+console.log("Called func2 at +${Date.now() - start}ms");
+
+console.log("END at +${Date.now() - start}ms");
diff --git a/tests/test_cpu_usage.cjs b/tests/test_cpu_usage.cjs
new file mode 100644
index 0000000..4b44b9c
--- /dev/null
+++ b/tests/test_cpu_usage.cjs
@@ -0,0 +1,8 @@
+async function test() {
+ console.log("Starting wait...");
+ await new Promise(resolve => setTimeout(resolve, 500));
+ console.log("Done!");
+ return "success";
+}
+
+test().then(r => console.log("Result:", r));
diff --git a/tests/test_debug_await.cjs b/tests/test_debug_await.cjs
new file mode 100644
index 0000000..c883a41
--- /dev/null
+++ b/tests/test_debug_await.cjs
@@ -0,0 +1,14 @@
+async function test() {
+ console.log("1: Before await");
+ const x = await new Promise(resolve => setTimeout(() => {
+ console.log("2: Timer callback");
+ resolve("VALUE");
+ }, 10));
+ console.log("3: x assigned, x =", x);
+ console.log("4: About to return");
+ return x;
+}
+
+console.log("A: Calling test");
+test().then(r => console.log("Final:", r));
+console.log("B: After test call");
diff --git a/tests/test_immediate_concurrent.cjs b/tests/test_immediate_concurrent.cjs
new file mode 100644
index 0000000..ca01919
--- /dev/null
+++ b/tests/test_immediate_concurrent.cjs
@@ -0,0 +1,21 @@
+// Test with IMMEDIATELY resolved promises (should work)
+console.log("=== Immediate Promises Test ===");
+
+async function func1() {
+ console.log("func1: start");
+ const result = await Promise.resolve("func1_result");
+ console.log("func1: after await, result:", result);
+ return result;
+}
+
+async function func2() {
+ console.log("func2: start");
+ const result = await Promise.resolve("func2_result");
+ console.log("func2: after await, result:", result);
+ return result;
+}
+
+func1().then(r => console.log("func1 final:", r));
+func2().then(r => console.log("func2 final:", r));
+
+console.log("=== Both called ===");
diff --git a/tests/test_practical_async.cjs b/tests/test_practical_async.cjs
new file mode 100644
index 0000000..3cf2829
--- /dev/null
+++ b/tests/test_practical_async.cjs
@@ -0,0 +1,29 @@
+// Real-world async pattern: sequential operations
+async function fetchUser(id) {
+ console.log(`Fetching user ${id}...`);
+ return new Promise(resolve => setTimeout(() => {
+ resolve({ id, name: `User${id}` });
+ }, 50));
+}
+
+async function fetchUserPosts(userId) {
+ console.log(`Fetching posts for user ${userId}...`);
+ return new Promise(resolve => setTimeout(() => {
+ resolve([`Post1 by ${userId}`, `Post2 by ${userId}`]);
+ }, 30));
+}
+
+async function getUserData(id) {
+ const user = await fetchUser(id);
+ console.log("Got user:", user.name);
+
+ const posts = await fetchUserPosts(user.id);
+ console.log("Got posts:", posts.length);
+
+ return { user, posts };
+}
+
+// This is the common pattern - sequential async calls within one function
+getUserData(123).then(data => {
+ console.log("Complete!", data.user.name, "has", data.posts.length, "posts");
+});
diff --git a/tests/test_two_awaits.cjs b/tests/test_two_awaits.cjs
new file mode 100644
index 0000000..dcfe4df
--- /dev/null
+++ b/tests/test_two_awaits.cjs
@@ -0,0 +1,32 @@
+// Test what happens when TWO async functions are awaiting simultaneously
+console.log("=== Multiple Awaits Test ===");
+
+async function delay(ms, name) {
+ console.log(`${name}: Creating ${ms}ms delay`);
+ return new Promise(resolve => {
+ setTimeout(() => {
+ console.log(`${name}: Timer fired after ${ms}ms`);
+ resolve(`${name}_RESULT`);
+ }, ms);
+ });
+}
+
+async function func1() {
+ console.log("func1: Before await");
+ const result = await delay(100, "func1");
+ console.log("func1: After await, result:", result);
+ return result;
+}
+
+async function func2() {
+ console.log("func2: Before await");
+ const result = await delay(50, "func2");
+ console.log("func2: After await, result:", result);
+ return result;
+}
+
+// Call both functions - they should run concurrently
+func1().then(r => console.log("func1 final:", r));
+func2().then(r => console.log("func2 final:", r));
+
+console.log("=== Both functions called ===");