Skip to content

Commit

Permalink
pass through arguments in timer functions
Browse files Browse the repository at this point in the history
- `setImmediate(fn, ...args)`
- `setInterval(fn, delay, ...args)`
- `setTimeout(fn, delay, ...args)`

fixes oven-sh#1633
  • Loading branch information
alexlamsl committed Dec 20, 2022
1 parent d7b73dd commit 60c6536
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 23 deletions.
85 changes: 70 additions & 15 deletions src/bun.js/bindings/ZigGlobalObject.cpp
Expand Up @@ -740,53 +740,90 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();

if (callFrame->argumentCount() == 0) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
auto argCount = callFrame->argumentCount();
if (argCount == 0) {
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::throwTypeError(globalObject, scope, "setTimeout requires 1 argument (a function)"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}

JSC::JSValue job = callFrame->argument(0);

if (!job.isObject() || !job.getObject()->isCallable()) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::throwTypeError(globalObject, scope, "setTimeout expects a function"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}

if (callFrame->argumentCount() == 1) {
if (argCount == 1) {
globalObject->queueMicrotask(job, JSC::JSValue {}, JSC::JSValue {},
JSC::JSValue {}, JSC::JSValue {});
return JSC::JSValue::encode(JSC::jsNumber(Bun__Timer__getNextID()));
}

JSC::JSValue num = callFrame->argument(1);
return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num));

if (argCount == 2) {
return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num));
}

JSC::JSArray* args = JSC::constructEmptyArray(globalObject, nullptr, argCount - 1);
if (UNLIKELY(!args)) {
auto scope = DECLARE_THROW_SCOPE(vm);
throwVMError(globalObject, scope, createOutOfMemoryError(globalObject));
return JSC::JSValue::encode(JSC::JSValue {});
}
args->putDirectIndex(globalObject, 0, JSC::jsNull());
for (unsigned i = 2; i < argCount; i++) {
args->putDirectIndex(globalObject, i - 1, callFrame->uncheckedArgument(i));
}

JSImmutableButterfly* boundArgs = JSImmutableButterfly::createFromArray(globalObject, vm, args);
JSObject* obj = job.getObject();
JSC::JSBoundFunction *bound = JSC::JSBoundFunction::create(vm, globalObject, obj, JSC::jsNull(), boundArgs, 0, nullptr);
return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(bound), JSC::JSValue::encode(num));
}

static JSC_DEFINE_HOST_FUNCTION(functionSetInterval,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();

if (callFrame->argumentCount() == 0) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
auto argCount = callFrame->argumentCount();
if (argCount == 0) {
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::throwTypeError(globalObject, scope, "setInterval requires 2 arguments (a function)"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}

JSC::JSValue job = callFrame->argument(0);

if (!job.isObject() || !job.getObject()->isCallable()) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::throwTypeError(globalObject, scope, "setInterval expects a function"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}

JSC::JSValue num = callFrame->argument(1);
return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job),
JSC::JSValue::encode(num));

if (argCount == 2) {
return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num));
}

JSC::JSArray* args = JSC::constructEmptyArray(globalObject, nullptr, argCount - 1);
if (UNLIKELY(!args)) {
auto scope = DECLARE_THROW_SCOPE(vm);
throwVMError(globalObject, scope, createOutOfMemoryError(globalObject));
return JSC::JSValue::encode(JSC::JSValue {});
}
args->putDirectIndex(globalObject, 0, JSC::jsNull());
for (unsigned i = 2; i < argCount; i++) {
args->putDirectIndex(globalObject, i - 1, callFrame->uncheckedArgument(i));
}

JSImmutableButterfly* boundArgs = JSImmutableButterfly::createFromArray(globalObject, vm, args);
JSObject* obj = job.getObject();
JSC::JSBoundFunction *bound = JSC::JSBoundFunction::create(vm, globalObject, obj, JSC::jsNull(), boundArgs, 0, nullptr);
return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(bound), JSC::JSValue::encode(num));
}

static JSC_DEFINE_HOST_FUNCTION(functionClearInterval,
Expand Down Expand Up @@ -2740,20 +2777,38 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetImmediate,
JSC::VM& vm = globalObject->vm();
auto argCount = callFrame->argumentCount();
if (argCount == 0) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::throwTypeError(globalObject, scope, "setImmediate requires 1 argument (a function)"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}

auto job = callFrame->argument(0);

if (!job.isObject() || !job.getObject()->isCallable()) {
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::throwTypeError(globalObject, scope, "setImmediate expects a function"_s);
return JSC::JSValue::encode(JSC::JSValue {});
}

return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(jsNumber(0)));
if (argCount == 1) {
return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(jsNumber(0)));
}

JSC::JSArray* args = JSC::constructEmptyArray(globalObject, nullptr, argCount);
if (UNLIKELY(!args)) {
auto scope = DECLARE_THROW_SCOPE(vm);
throwVMError(globalObject, scope, createOutOfMemoryError(globalObject));
return JSC::JSValue::encode(JSC::JSValue {});
}
args->putDirectIndex(globalObject, 0, JSC::jsNull());
for (unsigned i = 1; i < argCount; i++) {
args->putDirectIndex(globalObject, i, callFrame->uncheckedArgument(i));
}

JSImmutableButterfly* boundArgs = JSImmutableButterfly::createFromArray(globalObject, vm, args);
JSObject* obj = job.getObject();
JSC::JSBoundFunction *bound = JSC::JSBoundFunction::create(vm, globalObject, obj, JSC::jsNull(), boundArgs, 0, nullptr);
return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(bound), JSC::JSValue::encode(jsNumber(0)));
}

JSC_DEFINE_CUSTOM_GETTER(JSModuleLoader_getter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))
Expand Down
11 changes: 9 additions & 2 deletions test/bun.js/setInterval.test.js
Expand Up @@ -6,13 +6,20 @@ it("setInterval", async () => {
const result = await new Promise((resolve, reject) => {
start = performance.now();

var id = setInterval(() => {
var id = setInterval((...args) => {
counter++;
if (counter === 10) {
resolve(counter);
clearInterval(id);
}
}, 1);
try {
expect(args.length).toBe(1);
expect(args[0]).toBe("foo");
} catch (err) {
reject(err);
clearInterval(id);
}
}, 1, "foo");
});

expect(result).toBe(10);
Expand Down
19 changes: 13 additions & 6 deletions test/bun.js/setTimeout.test.js
Expand Up @@ -5,22 +5,29 @@ it("setTimeout", async () => {
const result = await new Promise((resolve, reject) => {
var numbers = [];

for (let i = 1; i < 100; i++) {
const id = setTimeout(() => {
for (let i = 0; i < 10; i++) {
const id = setTimeout((...args) => {
numbers.push(i);
if (i === 99) {
if (i === 9) {
resolve(numbers);
}
}, i);
try {
expect(args.length).toBe(1);
expect(args[0]).toBe("foo");
} catch (err) {
reject(err);
clearInterval(id);
}
}, i, "foo");
expect(id > lastID).toBe(true);
lastID = id;
}
});

for (let j = 0; j < result.length; j++) {
expect(result[j]).toBe(j + 1);
expect(result[j]).toBe(j);
}
expect(result.length).toBe(99);
expect(result.length).toBe(10);
});

it("clearTimeout", async () => {
Expand Down

0 comments on commit 60c6536

Please sign in to comment.