aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-10-24 04:10:44 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-10-24 04:10:44 -0700
commit0b0db78799308cc3de148959b9496901d90b0794 (patch)
tree161cf1c396c6e1534318c73bd5ce3e1c1ca787cf
parentb3434a8b883ee98324cea2c7d14dd988e6bb8adc (diff)
downloadbun-0b0db78799308cc3de148959b9496901d90b0794.tar.gz
bun-0b0db78799308cc3de148959b9496901d90b0794.tar.zst
bun-0b0db78799308cc3de148959b9496901d90b0794.zip
`Bun.peek`
Diffstat (limited to '')
-rw-r--r--README.md95
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp93
-rw-r--r--test/bun.js/peek.test.ts40
3 files changed, 228 insertions, 0 deletions
diff --git a/README.md b/README.md
index f0606d951..7da4e368b 100644
--- a/README.md
+++ b/README.md
@@ -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");
+});