aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/bun-types/bun.d.ts26
-rw-r--r--src/bun.js/api/bun.zig32
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp49
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h3
-rw-r--r--test/bun.js/setTimeout.test.js15
5 files changed, 110 insertions, 15 deletions
diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts
index a912eafa7..03f5c407c 100644
--- a/packages/bun-types/bun.d.ts
+++ b/packages/bun-types/bun.d.ts
@@ -2152,6 +2152,32 @@ declare module "bun" {
}
/**
+ * Resolve a `Promise` after milliseconds. This is like
+ * {@link setTimeout} except it returns a `Promise`.
+ *
+ * @param ms milliseconds to delay resolving the promise. This is a minimum number. It may take longer.
+ *
+ * @example
+ * ## Sleep for 1 second
+ * ```ts
+ * import { sleep } from "bun";
+ *
+ * await sleep(1000);
+ * ```
+ * ## Sleep for 10 milliseconds
+ * ```ts
+ * await Bun.sleep(10);
+ * ```
+ * Internally, `Bun.sleep` is the equivalent of
+ * ```ts
+ * await new Promise((resolve) => setTimeout(resolve, ms));
+ * ```
+ *
+ * As always, you can use `Bun.sleep` or the imported `sleep` function interchangeably.
+ */
+ export function sleep(ms: number): Promise<void>;
+
+ /**
* Sleep the thread for a given number of milliseconds
*
* This is a blocking function.
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index 0b47bea27..d7e42dc90 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -2873,19 +2873,25 @@ pub const Timer = struct {
const callback = this.callback.get() orelse @panic("Expected CallbackJob to have a callback function");
if (this.arguments.trySwap()) |arguments| {
- const count = arguments.getLengthOfArray(globalThis);
- if (count > 0) {
- if (count > args_buf.len) {
- args = bun.default_allocator.alloc(JSC.JSValue, count) catch unreachable;
- args_needs_deinit = true;
- } else {
- args = args_buf[0..count];
- }
- var arg = args.ptr;
- var i: u32 = 0;
- while (i < count) : (i += 1) {
- arg[0] = JSC.JSObject.getIndex(arguments, globalThis, @truncate(u32, i));
- arg += 1;
+ // Bun.sleep passes a Promise
+ if (arguments.jsType() == .JSPromise) {
+ args_buf[0] = arguments;
+ args = args_buf[0..1];
+ } else {
+ const count = arguments.getLengthOfArray(globalThis);
+ if (count > 0) {
+ if (count > args_buf.len) {
+ args = bun.default_allocator.alloc(JSC.JSValue, count) catch unreachable;
+ args_needs_deinit = true;
+ } else {
+ args = args_buf[0..count];
+ }
+ var arg = args.ptr;
+ var i: u32 = 0;
+ while (i < count) : (i += 1) {
+ arg[0] = JSC.JSObject.getIndex(arguments, globalThis, @truncate(u32, i));
+ arg += 1;
+ }
}
}
}
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index bfd775cde..7aa3a5bb9 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -713,7 +713,7 @@ JSC_DEFINE_CUSTOM_GETTER(lazyProcessEnvGetter,
globalObject->processEnvObject());
}
-static JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask,
+JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
@@ -741,7 +741,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask,
return JSC::JSValue::encode(JSC::jsUndefined());
}
-static JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,
+JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
@@ -790,6 +790,37 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,
return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments));
}
+JSC_DEFINE_HOST_FUNCTION(functionBunSleepThenCallback,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+
+ RELEASE_ASSERT(callFrame->argumentCount() == 1);
+ JSPromise* promise = jsCast<JSC::JSPromise*>(callFrame->argument(0));
+
+ // TODO: optimize this some more
+ promise->resolve(globalObject, JSC::jsUndefined());
+
+ return JSC::JSValue::encode(jsUndefined());
+}
+
+JSC_DEFINE_HOST_FUNCTION(functionBunSleep,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+
+ JSC::JSValue millisecondsValue = callFrame->argument(0);
+ if (!millisecondsValue.isNumber()) {
+ auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ JSC::throwTypeError(globalObject, scope, "sleep expects a number (milliseconds)"_s);
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ Zig::GlobalObject* global = JSC::jsCast<Zig::GlobalObject*>(globalObject);
+ JSC::JSPromise* promise = JSC::JSPromise::create(vm, globalObject->promiseStructure());
+ return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(global->bunSleepThenCallback()), JSC::JSValue::encode(millisecondsValue), JSValue::encode(promise));
+}
+
static JSC_DEFINE_HOST_FUNCTION(functionSetInterval,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
@@ -2490,6 +2521,11 @@ void GlobalObject::finishCreation(VM& vm)
init.set(JSFunction::create(init.vm, init.owner, 4, "emitReadable"_s, WebCore::jsReadable_emitReadable_, ImplementationVisibility::Public));
});
+ m_bunSleepThenCallback.initLater(
+ [](const Initializer<JSFunction>& init) {
+ init.set(JSFunction::create(init.vm, init.owner, 1, "onSleep"_s, functionBunSleepThenCallback, ImplementationVisibility::Public));
+ });
+
m_performMicrotaskVariadicFunction.initLater(
[](const Initializer<JSFunction>& init) {
init.set(JSFunction::create(init.vm, init.owner, 4, "performMicrotaskVariadic"_s, jsFunctionPerformMicrotaskVariadic, ImplementationVisibility::Public));
@@ -3344,6 +3380,13 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm
{
+ JSC::Identifier identifier = JSC::Identifier::fromString(vm, "sleep"_s);
+ object->putDirectNativeFunction(vm, this, identifier, 1, functionBunSleep, ImplementationVisibility::Public, NoIntrinsic,
+ JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
+ }
+
+ {
+
JSC::Identifier identifier = JSC::Identifier::fromString(vm, "env"_s);
object->putDirectCustomAccessor(vm, identifier,
JSC::CustomGetterSetter::create(vm, lazyProcessEnvGetter, lazyProcessEnvSetter),
@@ -3494,6 +3537,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
visitor.append(thisObject->m_JSFetchHeadersSetterValue);
visitor.append(thisObject->m_JSTextEncoderSetterValue);
visitor.append(thisObject->m_JSURLSearchParamsSetterValue);
+
thisObject->m_JSArrayBufferSinkClassStructure.visit(visitor);
thisObject->m_JSBufferListClassStructure.visit(visitor);
@@ -3529,6 +3573,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_requireResolveFunctionStructure.visit(visitor);
thisObject->m_resolveFunctionPrototype.visit(visitor);
thisObject->m_dnsObject.visit(visitor);
+ thisObject->m_bunSleepThenCallback.visit(visitor);
for (auto& barrier : thisObject->m_thenables) {
visitor.append(barrier);
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index f2364fd61..2b688f09d 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -247,6 +247,8 @@ public:
Structure* requireResolveFunctionStructure() { return m_requireResolveFunctionStructure.getInitializedOnMainThread(this); }
JSObject* requireResolveFunctionPrototype() { return m_resolveFunctionPrototype.getInitializedOnMainThread(this); }
+ JSFunction* bunSleepThenCallback() { return m_bunSleepThenCallback.getInitializedOnMainThread(this); }
+
JSObject* dnsObject() { return m_dnsObject.getInitializedOnMainThread(this); }
JSC::JSObject* processObject()
@@ -453,6 +455,7 @@ private:
LazyProperty<JSGlobalObject, JSC::Structure> m_requireResolveFunctionStructure;
LazyProperty<JSGlobalObject, JSObject> m_resolveFunctionPrototype;
LazyProperty<JSGlobalObject, JSObject> m_dnsObject;
+ LazyProperty<JSGlobalObject, JSFunction> m_bunSleepThenCallback;
DOMGuardedObjectSet m_guardedObjects WTF_GUARDED_BY_LOCK(m_gcLock);
void* m_bunVM;
diff --git a/test/bun.js/setTimeout.test.js b/test/bun.js/setTimeout.test.js
index 9cd16ece2..2670f8519 100644
--- a/test/bun.js/setTimeout.test.js
+++ b/test/bun.js/setTimeout.test.js
@@ -89,3 +89,18 @@ it("setTimeout(() => {}, 0)", async () => {
});
expect(ranFirst).toBe(-1);
});
+
+it("Bun.sleep", async () => {
+ var sleeps = 0;
+ await Bun.sleep(0);
+ const start = performance.now();
+ sleeps++;
+ await Bun.sleep(1);
+ sleeps++;
+ await Bun.sleep(2);
+ sleeps++;
+ const end = performance.now();
+ expect((end - start) * 1000).toBeGreaterThanOrEqual(3);
+
+ expect(sleeps).toBe(3);
+});