diff options
author | 2022-10-24 04:10:44 -0700 | |
---|---|---|
committer | 2022-10-24 04:10:44 -0700 | |
commit | 0b0db78799308cc3de148959b9496901d90b0794 (patch) | |
tree | 161cf1c396c6e1534318c73bd5ce3e1c1ca787cf | |
parent | b3434a8b883ee98324cea2c7d14dd988e6bb8adc (diff) | |
download | bun-0b0db78799308cc3de148959b9496901d90b0794.tar.gz bun-0b0db78799308cc3de148959b9496901d90b0794.tar.zst bun-0b0db78799308cc3de148959b9496901d90b0794.zip |
`Bun.peek`
Diffstat (limited to '')
-rw-r--r-- | README.md | 95 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 93 | ||||
-rw-r--r-- | test/bun.js/peek.test.ts | 40 |
3 files changed, 228 insertions, 0 deletions
@@ -139,6 +139,7 @@ bun upgrade --canary - [Error handling](#error-handling) - [`Bun.write` – optimizing I/O](#bunwrite--optimizing-io) - [`Bun.spawn` - spawn processes](#bunspawn) +- [`Bun.which` - find the path to a bin](#bunwhich) - [bun:sqlite (SQLite3 module)](#bunsqlite-sqlite3-module) - [bun:sqlite Benchmark](#bunsqlite-benchmark) - [Getting started with bun:sqlite](#getting-started-with-bunsqlite) @@ -173,6 +174,7 @@ bun upgrade --canary - [`Bun.Transpiler.transform`](#buntranspilertransform) - [`Bun.Transpiler.scan`](#buntranspilerscan) - [`Bun.Transpiler.scanImports`](#buntranspilerscanimports) +- [`Bun.peek` - read a promise same-tick](#bunpeek) - [Environment variables](#environment-variables) - [Credits](#credits) - [License](#license) @@ -2543,6 +2545,99 @@ interface Subprocess { } ``` +## `Bun.which` – find the path to a binary + +Find the path to an executable, similar to typing `which` in your terminal. + +```ts +const ls = Bun.which("ls"); +console.log(ls); // "/bin/ls" +``` + +`Bun.which` defaults the `PATH` to the current `PATH` environment variable, but you can customize it + +```ts +const ls = Bun.which("ls", { + PATH: "/usr/local/bin:/usr/bin:/bin", +}); +console.log(ls); // "/usr/bin/ls" +``` + +`Bun.which` also accepts a `cwd` option to search for the binary in a specific directory. + +```ts +const ls = Bun.which("ls", { + cwd: "/tmp", + PATH: "", +}); + +console.log(ls); // null +``` + +## `Bun.peek` - read a promise without resolving it + +`Bun.peek` is a utility function that lets you read a promise's result without `await` or `.then`, but only if the promise has already fulfilled or rejected. + +```ts +import { peek } from "bun"; + +const promise = Promise.resolve("hi"); +const result = await peek(promise); +console.log(result); // "hi" +``` + +`Bun.peek` is useful for performance-sensitive code that wants to reduce the number of extra microticks. It's an advanced API and you probably shouldn't use it unless you know what you're doing. + +```ts +import { peek } from "bun"; +import { expect, test } from "bun:test"; + +test("peek", () => { + const promise = Promise.resolve(true); + + // no await necessary! + expect(peek(promise)).toBe(true); + + // if we peek again, it returns the same value + const again = peek(promise); + expect(again).toBe(true); + + // if we peek a non-promise, it returns the value + const value = peek(42); + expect(value).toBe(42); + + // if we peek a pending promise, it returns the promise again + const pending = new Promise(() => {}); + expect(peek(pending)).toBe(pending); + + // If we peek a rejected promise, it: + // - returns the error + // - does not mark the promise as handled + const rejected = Promise.reject( + new Error("Succesfully tested promise rejection") + ); + expect(peek(rejected).message).toBe("Succesfully tested promise rejection"); +}); +``` + +`peek.status` lets you read the status of a promise without resolving it. + +```ts +import { peek } from "bun"; +import { expect, test } from "bun:test"; + +test("peek.status", () => { + const promise = Promise.resolve(true); + expect(peek.status(promise)).toBe("fulfilled"); + + const pending = new Promise(() => {}); + expect(peek.status(pending)).toBe("pending"); + + const rejected = Promise.reject(new Error("oh nooo")); + expect(peek.status(rejected)).toBe("rejected"); +}); +``` + ## `Bun.write` – optimizing I/O `Bun.write` lets you write, copy or pipe files automatically using the fastest system calls compatible with the input and platform. diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 71113e9a6..c19d3888b 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -2182,6 +2182,90 @@ void GlobalObject::finishCreation(VM& vm) RELEASE_ASSERT(classInfo()); } +JSC_DEFINE_HOST_FUNCTION(functionBunPeek, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue promiseValue = callFrame->argument(0); + if (UNLIKELY(!promiseValue)) { + return JSValue::encode(jsUndefined()); + } else if (!promiseValue.isCell()) { + return JSValue::encode(promiseValue); + } + + auto* promise = jsDynamicCast<JSPromise*>(promiseValue); + + if (!promise) { + return JSValue::encode(promiseValue); + } + + JSValue invalidateValue = callFrame->argument(1); + bool invalidate = invalidateValue.isBoolean() && invalidateValue.asBoolean(); + + switch (promise->status(vm)) { + case JSPromise::Status::Pending: { + break; + } + case JSPromise::Status::Fulfilled: { + JSValue result = promise->result(vm); + if (invalidate) { + promise->internalField(JSC::JSPromise::Field::ReactionsOrResult).set(vm, promise, jsUndefined()); + } + return JSValue::encode(result); + } + case JSPromise::Status::Rejected: { + JSValue result = promise->result(vm); + JSC::EnsureStillAliveScope ensureStillAliveScope(result); + + if (invalidate) { + promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, jsNumber(promise->internalField(JSC::JSPromise::Field::Flags).get().asUInt32() | JSC::JSPromise::isHandledFlag)); + promise->internalField(JSC::JSPromise::Field::ReactionsOrResult).set(vm, promise, JSC::jsUndefined()); + } + + return JSValue::encode(result); + } + } + + return JSValue::encode(promiseValue); +} + +JSC_DEFINE_HOST_FUNCTION(functionBunPeekStatus, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + static NeverDestroyed<String> fulfilled = MAKE_STATIC_STRING_IMPL("fulfilled"); + + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue promiseValue = callFrame->argument(0); + if (!promiseValue || !promiseValue.isCell()) { + return JSValue::encode(jsOwnedString(vm, fulfilled)); + } + + auto* promise = jsDynamicCast<JSPromise*>(promiseValue); + + if (!promise) { + return JSValue::encode(jsOwnedString(vm, fulfilled)); + } + + switch (promise->status(vm)) { + case JSPromise::Status::Pending: { + static NeverDestroyed<String> pending = MAKE_STATIC_STRING_IMPL("pending"); + return JSValue::encode(jsOwnedString(vm, pending)); + } + case JSPromise::Status::Fulfilled: { + return JSValue::encode(jsOwnedString(vm, fulfilled)); + } + case JSPromise::Status::Rejected: { + static NeverDestroyed<String> rejected = MAKE_STATIC_STRING_IMPL("rejected"); + return JSValue::encode(jsOwnedString(vm, rejected)); + } + } + + return JSValue::encode(jsUndefined()); +} + extern "C" void Bun__setOnEachMicrotaskTick(JSC::VM* vm, void* ptr, void (*callback)(void* ptr)) { if (callback == nullptr) { @@ -2547,6 +2631,15 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm } { + JSC::Identifier identifier = JSC::Identifier::fromString(vm, "peek"_s); + JSFunction* peekFunction = JSFunction::create(vm, this, 2, WTF::String("peek"_s), functionBunPeek, ImplementationVisibility::Public, NoIntrinsic); + JSFunction* peekStatus = JSFunction::create(vm, this, 1, WTF::String("status"_s), functionBunPeekStatus, ImplementationVisibility::Public, NoIntrinsic); + peekFunction->putDirect(vm, PropertyName(JSC::Identifier::fromString(vm, "status"_s)), peekStatus, JSC::PropertyAttribute::Function | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirect(vm, PropertyName(identifier), JSValue(peekFunction), + JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0); + } + + { JSC::Identifier identifier = JSC::Identifier::fromString(vm, "readableStreamToArrayBuffer"_s); object->putDirectBuiltinFunction(vm, this, identifier, readableStreamReadableStreamToArrayBufferCodeGenerator(vm), JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0); diff --git a/test/bun.js/peek.test.ts b/test/bun.js/peek.test.ts new file mode 100644 index 000000000..3b2237291 --- /dev/null +++ b/test/bun.js/peek.test.ts @@ -0,0 +1,40 @@ +import { peek } from "bun"; +import { expect, test } from "bun:test"; + +test("peek", () => { + const promise = Promise.resolve(true); + + // no await necessary! + expect(peek(promise)).toBe(true); + + // if we peek again, it returns the same value + const again = peek(promise); + expect(again).toBe(true); + + // if we peek a non-promise, it returns the value + const value = peek(42); + expect(value).toBe(42); + + // if we peek a pending promise, it returns the promise again + const pending = new Promise(() => {}); + expect(peek(pending)).toBe(pending); + + // If we peek a rejected promise, it: + // - returns the error + // - does not mark the promise as handled + const rejected = Promise.reject( + new Error("Succesfully tested promise rejection") + ); + expect(peek(rejected).message).toBe("Succesfully tested promise rejection"); +}); + +test("peek.status", () => { + const promise = Promise.resolve(true); + expect(peek.status(promise)).toBe("fulfilled"); + + const pending = new Promise(() => {}); + expect(peek.status(pending)).toBe("pending"); + + const rejected = Promise.reject(new Error("oh nooo")); + expect(peek.status(rejected)).toBe("rejected"); +}); |