TypeScript, Type Definitions, and Promisification
Contents
Got disgusted by JS's dynamic typing nature (same thing I hated Python). So I gave typescript another try. The one thing I care most is the support for async/await syntax, which it already supported.
Bare minimum TypeScript Setup
To setup a TypeScript project you need a tsconfig.json
:
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"noImplicitAny": false,
"sourceMap": false
},
"exclude": [
"node_modules",
"typings/browser.d.ts",
"typings/browser"
]
}
Compilation is then done with a simple tsc
command. This would compile your
scripts into .js
files.
Working with Type Definitions
To work with external libaries, you'll need to download a lot of .d.ts
type
definition files. That's where typings
comes into play. Install with:
npm i -g typings
Install type definitions with:
typings install node --save --ambient
Installed defs are put under typings
folder with the structure:
typings
│ browser.d.ts
│ main.d.ts
│
├───browser
│ └───ambient
│ ├───bluebird
│ │ index.d.ts
│ ├───commander
│ │ index.d.ts
│ └───node
│ index.d.ts
└───main
└───ambient
├───bluebird
│ index.d.ts
├───commander
│ index.d.ts
└───node
index.d.ts
The browser.d.ts
and main.d.ts
are top level definition for browser and server use respectively.
They contain the same content, simply referencing each installed .d.ts
files:
/// <reference path="main/ambient/bluebird/index.d.ts" /
/// <reference path="main/ambient/commander/index.d.ts"
/// <reference path="main/ambient/node/index.d.ts" />
Since they're duplicates to each other, and causes compilation warnings (a lot of them!)
if not treated. Therefore in tsconfig.json
you need to exclude
the portion (either broser or server) you don't intend to include when TypeScript compiles the project.
E.g., this project is a node project, so I excluded all browser ones by
specifying in tsconfig.json
:
"exclude": [
"typings/browser.d.ts",
"typings/browser"
]
Working with Promisified Node Modules
It's common to promisifyAll
a node module. Since you want to use the
*Async()
function variants, and they don't have definitions since they're
interpolated by bluebird
, sadly all intellisense and type checking for them
are gone.
import fs = require('fs');
import cp = require('child_process');
Promise.promisifyAll(fs);
Promise.promisifyAll(cp);
async function main() : Promise<string> {
let content : string = await fs.readFileAsync('./package.json', 'utf8');
console.log(content);
let out : string = await cp.execAsync('cmd.exe /c dir');
console.log(out);
return content;
}
The pattern I adopted is to type fs.readFile
, filling in the parameters except
the callback, and then append the Async
to the function name. Note that this
way tsc
would complain no such functions. An alternative to that is:
import fs = require('fs');
import cp = require('child_process');
let fs2 = Promise.promisifyAll(fs);
let cp2 = Promise.promisifyAll(cp);
async function main() : Promise<string> {
let content : string = await fs2.readFileAsync('./package.json', 'utf8');
console.log(content);
let out : string = await cp2.execAsync('cmd.exe /c dir');
console.log(out);
return content;
}
The benefit is now that compilation errors are gone. But when you need
intellisense, you would type fs.readFile
first, then convert that to
fs2.readFileAsync
when you're ready.
Theoretically speaking, someone would hand craft a promisified-node.d.ts
for
all the *Async()
functions. That took a lot of patience to me, but would be
greatly joyful to use:)