mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
console: print promise details (#4524)
This commit is contained in:
parent
3892d49165
commit
30fdf6dc83
5 changed files with 153 additions and 1 deletions
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import * as blob from "./web/blob.ts";
|
import * as blob from "./web/blob.ts";
|
||||||
import * as consoleTypes from "./web/console.ts";
|
import * as consoleTypes from "./web/console.ts";
|
||||||
|
import * as promiseTypes from "./web/promise.ts";
|
||||||
import * as customEvent from "./web/custom_event.ts";
|
import * as customEvent from "./web/custom_event.ts";
|
||||||
import * as domTypes from "./web/dom_types.ts";
|
import * as domTypes from "./web/dom_types.ts";
|
||||||
import * as domFile from "./web/dom_file.ts";
|
import * as domFile from "./web/dom_file.ts";
|
||||||
|
@ -102,6 +103,18 @@ declare global {
|
||||||
|
|
||||||
formatError: (e: Error) => string;
|
formatError: (e: Error) => string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get promise details as two elements array.
|
||||||
|
*
|
||||||
|
* First element is the `PromiseState`.
|
||||||
|
* If promise isn't pending, second element would be the result of the promise.
|
||||||
|
* Otherwise, second element would be undefined.
|
||||||
|
*
|
||||||
|
* Throws `TypeError` if argument isn't a promise
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getPromiseDetails<T>(promise: Promise<T>): promiseTypes.PromiseDetails<T>;
|
||||||
|
|
||||||
decode(bytes: Uint8Array): string;
|
decode(bytes: Uint8Array): string;
|
||||||
encode(text: string): Uint8Array;
|
encode(text: string): Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
|
@ -580,6 +580,27 @@ unitTest(function consoleTestStringifyIterable() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
unitTest(async function consoleTestStringifyPromises(): Promise<void> {
|
||||||
|
const pendingPromise = new Promise((_res, _rej) => {});
|
||||||
|
assertEquals(stringify(pendingPromise), "Promise { <pending> }");
|
||||||
|
|
||||||
|
const resolvedPromise = new Promise((res, _rej) => {
|
||||||
|
res("Resolved!");
|
||||||
|
});
|
||||||
|
assertEquals(stringify(resolvedPromise), `Promise { "Resolved!" }`);
|
||||||
|
|
||||||
|
let rejectedPromise;
|
||||||
|
try {
|
||||||
|
rejectedPromise = new Promise((_, rej) => {
|
||||||
|
rej(Error("Whoops"));
|
||||||
|
});
|
||||||
|
await rejectedPromise;
|
||||||
|
} catch (err) {}
|
||||||
|
const strLines = stringify(rejectedPromise).split("\n");
|
||||||
|
assertEquals(strLines[0], "Promise {");
|
||||||
|
assertEquals(strLines[1], " <rejected> Error: Whoops");
|
||||||
|
});
|
||||||
|
|
||||||
unitTest(function consoleTestWithCustomInspector(): void {
|
unitTest(function consoleTestWithCustomInspector(): void {
|
||||||
class A {
|
class A {
|
||||||
[customInspect](): string {
|
[customInspect](): string {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { TextEncoder } from "./text_encoding.ts";
|
||||||
import { File, stdout } from "../files.ts";
|
import { File, stdout } from "../files.ts";
|
||||||
import { cliTable } from "./console_table.ts";
|
import { cliTable } from "./console_table.ts";
|
||||||
import { exposeForTest } from "../internals.ts";
|
import { exposeForTest } from "../internals.ts";
|
||||||
|
import { PromiseState } from "./promise.ts";
|
||||||
|
|
||||||
type ConsoleContext = Set<unknown>;
|
type ConsoleContext = Set<unknown>;
|
||||||
type InspectOptions = Partial<{
|
type InspectOptions = Partial<{
|
||||||
|
@ -28,6 +29,8 @@ const CHAR_LOWERCASE_O = 111; /* o */
|
||||||
const CHAR_UPPERCASE_O = 79; /* O */
|
const CHAR_UPPERCASE_O = 79; /* O */
|
||||||
const CHAR_LOWERCASE_C = 99; /* c */
|
const CHAR_LOWERCASE_C = 99; /* c */
|
||||||
|
|
||||||
|
const PROMISE_STRING_BASE_LENGTH = 12;
|
||||||
|
|
||||||
export class CSI {
|
export class CSI {
|
||||||
static kClear = "\x1b[1;1H";
|
static kClear = "\x1b[1;1H";
|
||||||
static kClearScreenDown = "\x1b[0J";
|
static kClearScreenDown = "\x1b[0J";
|
||||||
|
@ -442,7 +445,34 @@ function createNumberWrapperString(value: Number): string {
|
||||||
|
|
||||||
/* eslint-enable @typescript-eslint/ban-types */
|
/* eslint-enable @typescript-eslint/ban-types */
|
||||||
|
|
||||||
// TODO: Promise, requires v8 bindings to get info
|
function createPromiseString(
|
||||||
|
value: Promise<unknown>,
|
||||||
|
ctx: ConsoleContext,
|
||||||
|
level: number,
|
||||||
|
maxLevel: number
|
||||||
|
): string {
|
||||||
|
const [state, result] = Deno.core.getPromiseDetails(value);
|
||||||
|
|
||||||
|
if (state === PromiseState.Pending) {
|
||||||
|
return "Promise { <pending> }";
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = state === PromiseState.Fulfilled ? "" : "<rejected> ";
|
||||||
|
|
||||||
|
const str = `${prefix}${stringifyWithQuotes(
|
||||||
|
result,
|
||||||
|
ctx,
|
||||||
|
level + 1,
|
||||||
|
maxLevel
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) {
|
||||||
|
return `Promise {\n${" ".repeat(level + 1)}${str}\n}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Promise { ${str} }`;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Proxy
|
// TODO: Proxy
|
||||||
|
|
||||||
function createRawObjectString(
|
function createRawObjectString(
|
||||||
|
@ -531,6 +561,8 @@ function createObjectString(
|
||||||
return createBooleanWrapperString(value);
|
return createBooleanWrapperString(value);
|
||||||
} else if (value instanceof String) {
|
} else if (value instanceof String) {
|
||||||
return createStringWrapperString(value);
|
return createStringWrapperString(value);
|
||||||
|
} else if (value instanceof Promise) {
|
||||||
|
return createPromiseString(value, ...args);
|
||||||
} else if (value instanceof RegExp) {
|
} else if (value instanceof RegExp) {
|
||||||
return createRegExpString(value);
|
return createRegExpString(value);
|
||||||
} else if (value instanceof Date) {
|
} else if (value instanceof Date) {
|
||||||
|
|
7
cli/js/web/promise.ts
Normal file
7
cli/js/web/promise.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export enum PromiseState {
|
||||||
|
Pending = 0,
|
||||||
|
Fulfilled = 1,
|
||||||
|
Rejected = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PromiseDetails<T> = [PromiseState, T | undefined];
|
|
@ -45,6 +45,9 @@ lazy_static! {
|
||||||
v8::ExternalReference {
|
v8::ExternalReference {
|
||||||
function: decode.map_fn_to()
|
function: decode.map_fn_to()
|
||||||
},
|
},
|
||||||
|
v8::ExternalReference {
|
||||||
|
function: get_promise_details.map_fn_to(),
|
||||||
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +198,17 @@ pub fn initialize_context<'s>(
|
||||||
decode_val.into(),
|
decode_val.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut get_promise_details_tmpl =
|
||||||
|
v8::FunctionTemplate::new(scope, get_promise_details);
|
||||||
|
let get_promise_details_val = get_promise_details_tmpl
|
||||||
|
.get_function(scope, context)
|
||||||
|
.unwrap();
|
||||||
|
core_val.set(
|
||||||
|
context,
|
||||||
|
v8::String::new(scope, "getPromiseDetails").unwrap().into(),
|
||||||
|
get_promise_details_val.into(),
|
||||||
|
);
|
||||||
|
|
||||||
core_val.set_accessor(
|
core_val.set_accessor(
|
||||||
context,
|
context,
|
||||||
v8::String::new(scope, "shared").unwrap().into(),
|
v8::String::new(scope, "shared").unwrap().into(),
|
||||||
|
@ -761,3 +775,68 @@ pub fn module_resolve_callback<'s>(
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns promise details or throw TypeError, if argument passed isn't a Promise.
|
||||||
|
// Promise details is a two elements array.
|
||||||
|
// promise_details = [State, Result]
|
||||||
|
// State = enum { Pending = 0, Fulfilled = 1, Rejected = 2}
|
||||||
|
// Result = PromiseResult<T> | PromiseError
|
||||||
|
fn get_promise_details(
|
||||||
|
scope: v8::FunctionCallbackScope,
|
||||||
|
args: v8::FunctionCallbackArguments,
|
||||||
|
mut rv: v8::ReturnValue,
|
||||||
|
) {
|
||||||
|
let deno_isolate: &mut Isolate =
|
||||||
|
unsafe { &mut *(scope.isolate().get_data(0) as *mut Isolate) };
|
||||||
|
assert!(!deno_isolate.global_context.is_empty());
|
||||||
|
let context = deno_isolate.global_context.get(scope).unwrap();
|
||||||
|
|
||||||
|
let mut promise = match v8::Local::<v8::Promise>::try_from(args.get(0)) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => {
|
||||||
|
let msg = v8::String::new(scope, "Invalid argument").unwrap();
|
||||||
|
let exception = v8::Exception::type_error(scope, msg);
|
||||||
|
scope.isolate().throw_exception(exception);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let promise_details = v8::Array::new(scope, 2);
|
||||||
|
|
||||||
|
match promise.state() {
|
||||||
|
v8::PromiseState::Pending => {
|
||||||
|
promise_details.set(
|
||||||
|
context,
|
||||||
|
v8::Integer::new(scope, 0).into(),
|
||||||
|
v8::Integer::new(scope, 0).into(),
|
||||||
|
);
|
||||||
|
rv.set(promise_details.into());
|
||||||
|
}
|
||||||
|
v8::PromiseState::Fulfilled => {
|
||||||
|
promise_details.set(
|
||||||
|
context,
|
||||||
|
v8::Integer::new(scope, 0).into(),
|
||||||
|
v8::Integer::new(scope, 1).into(),
|
||||||
|
);
|
||||||
|
promise_details.set(
|
||||||
|
context,
|
||||||
|
v8::Integer::new(scope, 1).into(),
|
||||||
|
promise.result(scope),
|
||||||
|
);
|
||||||
|
rv.set(promise_details.into());
|
||||||
|
}
|
||||||
|
v8::PromiseState::Rejected => {
|
||||||
|
promise_details.set(
|
||||||
|
context,
|
||||||
|
v8::Integer::new(scope, 0).into(),
|
||||||
|
v8::Integer::new(scope, 2).into(),
|
||||||
|
);
|
||||||
|
promise_details.set(
|
||||||
|
context,
|
||||||
|
v8::Integer::new(scope, 1).into(),
|
||||||
|
promise.result(scope),
|
||||||
|
);
|
||||||
|
rv.set(promise_details.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue