mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -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 consoleTypes from "./web/console.ts";
|
||||
import * as promiseTypes from "./web/promise.ts";
|
||||
import * as customEvent from "./web/custom_event.ts";
|
||||
import * as domTypes from "./web/dom_types.ts";
|
||||
import * as domFile from "./web/dom_file.ts";
|
||||
|
@ -102,6 +103,18 @@ declare global {
|
|||
|
||||
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;
|
||||
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 {
|
||||
class A {
|
||||
[customInspect](): string {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { TextEncoder } from "./text_encoding.ts";
|
|||
import { File, stdout } from "../files.ts";
|
||||
import { cliTable } from "./console_table.ts";
|
||||
import { exposeForTest } from "../internals.ts";
|
||||
import { PromiseState } from "./promise.ts";
|
||||
|
||||
type ConsoleContext = Set<unknown>;
|
||||
type InspectOptions = Partial<{
|
||||
|
@ -28,6 +29,8 @@ const CHAR_LOWERCASE_O = 111; /* o */
|
|||
const CHAR_UPPERCASE_O = 79; /* O */
|
||||
const CHAR_LOWERCASE_C = 99; /* c */
|
||||
|
||||
const PROMISE_STRING_BASE_LENGTH = 12;
|
||||
|
||||
export class CSI {
|
||||
static kClear = "\x1b[1;1H";
|
||||
static kClearScreenDown = "\x1b[0J";
|
||||
|
@ -442,7 +445,34 @@ function createNumberWrapperString(value: Number): string {
|
|||
|
||||
/* 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
|
||||
|
||||
function createRawObjectString(
|
||||
|
@ -531,6 +561,8 @@ function createObjectString(
|
|||
return createBooleanWrapperString(value);
|
||||
} else if (value instanceof String) {
|
||||
return createStringWrapperString(value);
|
||||
} else if (value instanceof Promise) {
|
||||
return createPromiseString(value, ...args);
|
||||
} else if (value instanceof RegExp) {
|
||||
return createRegExpString(value);
|
||||
} 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 {
|
||||
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(),
|
||||
);
|
||||
|
||||
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(
|
||||
context,
|
||||
v8::String::new(scope, "shared").unwrap().into(),
|
||||
|
@ -761,3 +775,68 @@ pub fn module_resolve_callback<'s>(
|
|||
|
||||
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