diff options
-rw-r--r-- | packages/bun-types/bun.d.ts | 26 | ||||
-rw-r--r-- | src/bun.js/api/bun.zig | 32 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 49 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.h | 3 | ||||
-rw-r--r-- | test/bun.js/setTimeout.test.js | 15 |
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); +}); |