Definitelytyped: [types/node] Support for Node 8.0, especially "Improved support for Promises"

Created on 31 May 2017  路  20Comments  路  Source: DefinitelyTyped/DefinitelyTyped

It would be nice if types/node would support the new features of Node 8.0, especially "Improved support for Promises" https://nodejs.org/en/blog/release/v8.0.0#header-improved-support-for-promises.

Most helpful comment

Another workaround with up to 3 typed arguments:

import * as util from 'util';

interface NodeCallback<T> {
  (err: any, result?: T): void;
}
interface NodeCallback2<T> {
  (result: T): void;
}

declare module "util" {
  export function promisify<T>(f: (callback?: NodeCallback<undefined>) => void): () => Promise<T>;
  export function promisify<T, S>(f: (arg1: S, callback: NodeCallback<T>) => void): (arg1: S) => Promise<T>;
  export function promisify<T, S, U>(f: (arg1: S, arg2: U, callback: NodeCallback<T>) => void): (arg1: S, arg2: U) => Promise<T>;
  export function promisify<T, S, U, W>(f: (arg1: S, arg2: U, arg3: W, callback: NodeCallback<T>) => void): (arg1: S, arg2: U, arg3: W) => Promise<T>;
  export function promisify<T>(f: (callback: NodeCallback2<undefined>) => void): () => Promise<T>;
  export function promisify<T, S>(f: (arg1: S, callback: NodeCallback2<T>) => void): (arg1: S) => Promise<T>;
  export function promisify<T, S, U>(f: (arg1: S, arg2: U, callback: NodeCallback2<T>) => void): (arg1: S, arg2: U) => Promise<T>;
  export function promisify<T, S, U, W>(f: (arg1: S, arg2: U, arg3: W, callback: NodeCallback2<T>) => void): (arg1: S, arg2: U, arg3: W) => Promise<T>;
}

All 20 comments

Since this is a major version change, we should also take the opportunity to make any necessary breaking changes to the node types.
One is to make process.env not any.
There are probably others.
CC @xeoneux @blakeembrey

Temporary workaround for util.promisify for those that may be interested:

declare module 'util' {
  export function promisify<T>(
    func: (data: any, cb: (err: NodeJS.ErrnoException, data?: T) => void,
  ) => void): (...input: any[]) => Promise<T>;
}

(works with readdir and writeFile, not sure if this is enough to cover all the other potential cases)

@dsifford I don't think there's any guarantee that the input function has the callback as the second argument. It uses the spread operator to call the original function.

Another workaround with up to 3 typed arguments:

import * as util from 'util';

interface NodeCallback<T> {
  (err: any, result?: T): void;
}
interface NodeCallback2<T> {
  (result: T): void;
}

declare module "util" {
  export function promisify<T>(f: (callback?: NodeCallback<undefined>) => void): () => Promise<T>;
  export function promisify<T, S>(f: (arg1: S, callback: NodeCallback<T>) => void): (arg1: S) => Promise<T>;
  export function promisify<T, S, U>(f: (arg1: S, arg2: U, callback: NodeCallback<T>) => void): (arg1: S, arg2: U) => Promise<T>;
  export function promisify<T, S, U, W>(f: (arg1: S, arg2: U, arg3: W, callback: NodeCallback<T>) => void): (arg1: S, arg2: U, arg3: W) => Promise<T>;
  export function promisify<T>(f: (callback: NodeCallback2<undefined>) => void): () => Promise<T>;
  export function promisify<T, S>(f: (arg1: S, callback: NodeCallback2<T>) => void): (arg1: S) => Promise<T>;
  export function promisify<T, S, U>(f: (arg1: S, arg2: U, callback: NodeCallback2<T>) => void): (arg1: S, arg2: U) => Promise<T>;
  export function promisify<T, S, U, W>(f: (arg1: S, arg2: U, arg3: W, callback: NodeCallback2<T>) => void): (arg1: S, arg2: U, arg3: W) => Promise<T>;
}

Now accepting PRs.

See #17458 for correct process.env type

Any progress on this one? I saw that I can use util.promisify with the latest @types/node definition 8.0.19 but unfortunately I doesn't recognise function overloads. It only provides me the latest overload and ignores all other overloads of a function.

There is already a closed issue at https://github.com/Microsoft/TypeScript/issues/16348 but without any solution.

@screendriver Could you provide an example that works in node but is a compile error in TS?
CC @rbuckton who worked on the __promisify__ implementation.

Our type system has no way to infer overloads from call signatures that apply to a generic type argument.

As a workaround I added __promisify__ as a way to flow the overloads for functions through promisify similar to how promisify itself has a custom symbol to allow customization at runtime.

@screendriver Could you provide an example that works in node but is a compile error in TS?

import * as util from 'util';

type CallbackFn = (err: NodeJS.ErrnoException, value: string) => void;

function foo(one: string, callback: CallbackFn);
function foo(two: number, callback: CallbackFn);
function foo(arg?: string | number, callback?: CallbackFn) {

}

util.promisify(foo)('bar'); // Compiler error because it only see the number overload

@andy-ms

You need to use __promisify__:

type CallbackFn = (err: NodeJS.ErrnoException, value: string) => void;
function foo(one: string, callback: CallbackFn): void;
function foo(two: number, callback: CallbackFn): void;
function foo(arg?: string | number, callback?: CallbackFn) {}
declare namespace foo {
    function __promisify__(one: string): Promise<string>;
    function __promisify__(two: number): Promise<string>;
}
const x = util.promisify(foo)("bar");
const y = util.promisify(foo)(1);

(In your case, though, you could just take a string | number union and not bother with overloads. Then using __promisify__ is unnecessary.)

@andy-ms : does that work with setTimeout though? as per https://nodejs.org/api/timers.html#timers_settimeout_callback_delay_args

I think the definition for Buffer.from() also has to be changed to accept ArrayBufferLike instead of just ArrayBuffer. This prevents this error:

Error:(-, -) TS2345:Argument of type 'SharedArrayBuffer | ArrayBuffer' is not assignable to parameter of type 'string'.
  Type 'SharedArrayBuffer' is not assignable to type 'string'.

@ThomasdenH PRs accepted.
Also, would you know if the first overload of from should take number[] instead of any[]?

Is it enough to update the definition to accept a SharedArrayBuffer? The type is new for ES2017, so I don't know if there will be any compatibility issues in that case?

As for the array type, I think the creation ultimately uses this function. I think since it overrides Uint8Array it behaves the same, for which the spec says the function ToInt8 is used.

If the input will be implicitly converted to numbers, I think it would be better to just type it as a number[] in the first place. But you don't have to include that as part of a PR that adds SharedArrayBuffer support.

@rbuckton, I'm trying to understand how __promisify__ works, and I fail to find where it is implemented.
Is it somewhere in the TypeScript compiler?

__promisify__ is used to override the type returned by Node's promisify function exported by the "util" module:

import { writeFile } from "fs";
import { promisify } from "util";

const writeFileAsync = promisify(writeFile); // uses the `__promisify__ declaration on `writeFile` to correctly type the return value

async function foo() {
  await writeFileAsync("test", "output.txt");
}

At runtime, NodeJS actually uses a symbol attached to certain functions to override the behavior of util.promisify, but we have no way to easily augment the type of a function declaration with a symbol-named export in TypeScript today, so __promisify__ acts as a stand-in for that symbol.

See the declarations of promisify and CustomPromisify in the _util.d.ts_ file of the Node typings to see how its used.

Oh, thanks. I thought __promisify__ was a special case in a TypeScript compiler or something like that. Now I see that it is just a funny name rather than a special case. Thanks.

Was this page helpful?
0 / 5 - 0 ratings