TypeScriptの–strictNullChecksでnullやundefinedの代入をチェックする

TypeScriptでは、すべての型の変数にnullやundefinedを代入できます。

let str: string = "Hello";
str = null; // OK
str = undefined; // OK

strictNullChecksモードを有効にすると、nullとundefinedはそれぞれの型とvoid以外の変数に代入できなくなります。

strictNullChecksモードを有効にします。

tsc index.ts --strictNullChecks

今度はstring型の変数にnullやundefinedを代入すると、エラーになります。

let str: string = "Hello";
str = null; // error TS2322: Type 'null' is not assignable to type 'string'.
str = undefined; // error TS2322: Type 'undefined' is not assignable to type 'string'.

strictNullChecksモードを有効にすることで、nullやundefinedによるエラーを減らすことができます。

TypeScriptでサーバーからファイルをダウンロードして、ローカルに保存するサンプルコード

TypeScriptでサーバーからファイルをダウンロードして、ローカルに保存するサンプルコード。

imagesディレクトリを作成して、ファイルを保存します。
ダウンロードするファイルと保存するファイルのファイル名は、download()関数の引数で設定します。

事前準備

npm install node-fetch --save
npm install @types/node-fetch --save

ソースコード

import * as fs from "fs";
import * as path from "path";
import fetch from "node-fetch";

// ダウンロードしたファイルを保存するディレクトリ
const download_dir = path.join(process.cwd(), "images");

// ディレクトリがなければ作成する
if (!fs.existsSync(download_dir)) {
    fs.mkdirSync(download_dir);
}

/**
* URLを受け取り、該当するファイルをダウンロードして保存する
* @param url ダウンロードするファイルのURL
* @param filename 保存するファイルのファイル名
*/
function download(url: string, filename: string) {
    fetch(url, { method: "GET" }).then((response) => {
        console.log(`OK: ${filename}`);
        response.body.pipe(fs.createWriteStream(filename));
    }).catch((error) => console.log(`error:${url} ${error}`));
};

download(
    "http://~/example.jpg",
    path.join(download_dir, "sample.jpg"));

TypeScriptでMap

Mapはキーと値の組み合わせを保持するコレクションです。

前準備

TypeScriptをインストールします。

npm install -g typescript
npm install -g typings

プロジェクトの初期設定を行います。

npm init
tsc --init
npm install tslint --save-dev
tslint --init

定義ファイルのインストール

npm install --save-dev @types/core-js

使用例

// Mapオブジェクトを生成する(キーと値の型はstring)
let map = new Map<string, string>();

// 値を追加する
map.set("key1", "value1");
map.set("key2", "value2");

// 要素の数
console.log(map.size); //=> 2

// キーから値を取得する
console.log(map.get("key1")); //=> value1
// キーが存在しないとき
console.log(map.get("hoge")); //=> undefined

// キーの一覧を取得する
console.log(map.keys()); //=> MapIterator { 'key1', 'key2' }

// 値の一覧を取得する
console.log(map.values()); //=> MapIterator { 'value1', 'value2' }

// キーと値の一覧を取得する
console.log(map.entries()); //=> MapIterator { [ 'key1', 'value1' ], [ 'key2', 'value2' ] }

// 反復処理
map.forEach(
    (value: string, key: string) => console.log(key + "=" + value)
);

TypeScriptでSet

Setは値が重複しないコレクションです。
同じ値は一つしか登録されません。

前準備

TypeScriptをインストールします。

npm install -g typescript
npm install -g typings

プロジェクトの初期設定を行います。

npm init
tsc --init
npm install tslint --save-dev
tslint --init

定義ファイルのインストール

npm install --save-dev @types/core-js

使用例

// オブジェクトを生成します
let set = new Set<string>(["node.js", "TypeScript"]);
// 値を追加します
set.add("JavaScript");
// 登録されている値の数
console.log(set.size); // => 3
// 値の有無
console.log(set.has("TypeScript")); // => true
// 値を削除する
set.delete("TypeScript");
// 値の有無
console.log(set.has("TypeScript")); // => false
// 値を順番に出力する
set.forEach((value) => console.log(value)); // => "node.js" "TypeScript"

Setオブジェクトを作成する

let set = new Set<string>();

引数で初期値を登録できます。

let set = new Set<string>(["node.js", "TypeScript"]);

値を追加する

addメソッドで値を登録します。

set.add("JavaScript");
set.add("Python");
set.add("C++");

値を削除する

deleteメソッドで値を削除します。

set.delete("Python");

値をクリアする

clearメソッドですべての値を削除します。

set.clear();

値に対して関数を実行する

forEachメソッドで値に対して関数を実行します。

set.forEach(value => console.log(value));

値の有無を調べる

let exist = set.has("TypeScript");

Iteratorを取得する

keysメソッドとvaluesメソッドは、どちらも値を登録した順番を保持するIteratorオブジェクトを返します。

console.log(set.keys());
//=> SetIterator { 'node.js', 'TypeScript', 'JavaScript' }

iterable

Setはiterableです。

Array.from()はiterableを受け取り、Arrayオブジェクトを生成します。

let ary = Array.from(set);

TypeScriptでAsync/Await

TypeScriptでAsync/Awaitを使ってみる。

動作環境

> tsc --version
Version 2.2.1

やりたいこと

サンプルとして、非同期で実行する3つの処理がある。
func1()、func2()、func3()とする。

func1()は”1秒後”に”1″を出力する。
func2()は”2秒後”に”2″を出力する。
func3()は”3秒後”に”3″を出力する。

function func1() {
    setTimeout(() => {
        console.log(1);
    }, 1000);
}

function func2() {
    setTimeout(() => {
        console.log(2);
    }, 2000);
}

function func3() {
    setTimeout(() => {
        console.log(3);
    }, 3000);
}

これを、321の順番に出力したい。

そのまま実行する

function func1() {
    setTimeout(() => {
        console.log(1);
    }, 1000);
}

function func2() {
    setTimeout(() => {
        console.log(2);
    }, 2000);
}

function func3() {
    setTimeout(() => {
        console.log(3);
    }, 3000);
}

function main() {
    func3();
    func2();
    func1();
}

main();

実行結果

1
2
3

関数は、func3()・func2()・func1()の順番に実行しても、出力は”123″の順番になる。

Promiseを使う

Promiseを使って書き直す。

func1()・func2()・func3()はPromiseを返すように変更する。

function func1(): Promise<any> {
    return new Promise<any>(resolve => {
        setTimeout(() => {
            console.log(1);
            resolve();
        }, 1000);
    });
}
function func2(): Promise<any> {
    return new Promise<any>(resolve => {
        setTimeout(() => {
            console.log(2);
            resolve();
        }, 2000);
    });
}
function func3(): Promise<any> {
    return new Promise<any>(resolve => {
        setTimeout(() => {
            console.log(3);
            resolve();
        }, 3000);
    });
}

Promiseのthen()を使って、順番に実行する。

function main() {
    func3().then(resolve => {
        return func2();
    })
    .then(resoluve => {
        return func1();
    });
}

main();

実行結果

3
2
1

期待する結果が得られた。

Async Awaitを使う

main()関数を修正する。

async function main() {
    await func3();
    await func2();
    await func1();
}

実行結果

3
2
1

期待する結果が得られた。

Promiseのthen()を使っていたコードよりも簡潔にコードを記述できた。

awaitをつけると、Promiseが終了するまで次の処理が始まらない。
awaitは、asyncがついた関数の中でなければ利用できない。

戻り値を受け取る

awaitを使うとき、戻り値を受け取ることができる。

次のコードでは、func1()が返すPromiseの戻り値を受け取り、出力する。

function func1(): Promise<string> {
    return new Promise<string>(resolve => {
        setTimeout(() => {
            console.log(1);
            resolve("OK");
        }, 1000);
    });
}

async function main() {
    const ret = await func1();
    console.log(ret);
}

main();

TypeScriptでPromiseを使う

執筆時のバージョン

> tsc --version
Version 2.1.6

定義ファイルのインストール

定義ファイルは、以下のようにnpmで直接インストールすることができます。

npm install --save @types/es6-promise

Promiseを使う

// Promise<T>のTはresolveの引数の型
const p1 = new Promise<string>((resolve: (value?: string) => void, reject: (reason?: any) => void) => {
    // 非同期の処理
    // 成功したときは戻り値を引数にしてresolveを呼ぶ
    // 失敗したときは戻り値を引数にしてrejectを呼ぶ
    setTimeout(() => {
        resolve("OK");  // 成功したときの戻り値
    }, 1000);
}).then(
    // Promiseが成功したとき
    (value: string) => { // 引数valueの型はProcese<T>のT型
        console.log(value);
    }
).catch(
    // Promiseが失敗したとき
    (reason: any) => {
        console.log(reason);
    }
);

Promiseのコンストラクタは、関数を引数に取ります。
引数の関数は、resolveとrejectを引数に取り、戻り値はありません。
resolveとrejectはどちらも関数です。

const p1 = new Promise<string>(
    (resolve: (value?: string) => void, reject: (reason?: any) => void) => {
});

Promiseの型引数Tはresolveの引数の型です。

引数の関数の中で、非同期処理を実行します。
処理が成功したら、戻り値を引数にしてresolve()を呼びます。
失敗したときは、エラーの内容を引数にしてreject()を呼びます。

const p1 = new Promise<string>(
    (resolve: (value?: string) => void, reject: (reason?: any) => void) => {
    // 非同期の処理
    // 成功したときは戻り値を引数にしてresolveを呼ぶ
    // 失敗したときは戻り値を引数にしてrejectを呼ぶ
    setTimeout(() => {
        resolve("OK");  // 成功したときの戻り値
    }, 1000);
});

then()メソッドに、処理が成功したときに実行するコールバック関数を登録します。
関数の引数の型は、Promiseで指定したT型です。

const p1 = new Promise<string>((resolve: (value?: string) => void, reject: (reason?: any) => void) => {
    // 非同期の処理
}).then(
    // Promiseが成功したとき
    (value: string) => { // 引数valueの型はProcese<T>のT型
        console.log(value);
    }
);

catch()メソッドに、処理が失敗したときに実行するコールバック関数を登録します。

const p1 = new Promise<string>((resolve: (value?: string) => void, reject: (reason?: any) => void) => {
    // 非同期の処理
}).catch(
    // Promiseが失敗したとき
    (reason: any) => {
        console.log(reason);
    }
);

Promise.all

複数の非同期処理がすべて終わったときに処理を行いたいときは、Promise.all()を使用します。

Promise.all()はPromiseオブジェクトの配列を受け取ります。
すべてのPromiseオブジェクトがresolveされると、then()メソッドを呼びます。

サンプルコード

Promise.all([
    // Promiseオブジェクトの配列
    new Promise((resolve, reject) => { setTimeout(() => resolve("1"), 1000); }),
    new Promise((resolve, reject) => { setTimeout(() => resolve("2"), 2000); }),
    new Promise((resolve, reject) => { setTimeout(() => resolve("3"), 3000); }),
]).then(
    // すべてのPromiseオブジェクトがresolveされた後に呼ばれる
    value => console.log(value)
);

実行結果

[ '1', '2', '3' ]

node.jsでCSVを作成するには

node-csvを使って、CSVファイルを作成する方法です。

インストール

npm init
npm install csv --save

文字列の配列をCSVに変換する(callback)

callbackを使ったTypeScriptのサンプルコードです。

csv.stringify()の引数に、文字列の配列とコールバック関数を設定します。
CSVに変換された文字列がコールバック関数の引数outputになります。

import * as csv from "csv";

const input = [ [ "1", "2", "3", "4" ], [ "a", "b", "c", "d" ] ];
csv.stringify(input, function(err, output){
    console.log(output);
});

実行結果

1,2,3,4
a,b,c,d

文字列の配列をCSVに変換する(stream)

streamを使ったTypeScriptのサンプルコードです。

write()メソッドの引数に文字列の配列を指定します。

実行すると、readableイベントで変換された文字列を受け取ることができます。
read()メソッドで各行の文字列を受け取ります。

import * as csv from "csv";

let data = "";
const stringifier = csv.stringify({ delimiter: ":" })
stringifier.on("readable", function () {
    let row;
    while (row = stringifier.read()) {
        data += row;
    }
});
stringifier.on("error", function (err) {
    console.log(err.message);
});
stringifier.on("finish", function () {
    console.log(data);
});
stringifier.write(["1", "2", "3", "4"]);
stringifier.write(["a", "b", "c", "d"]);
stringifier.end();

オブジェクトの配列をCSVに変換する

引数にオブジェクトを指定することもできます。

import * as csv from "csv";

const input = [
    {Rider: "MARQUEZ Marc", Nation: "SPA", Points: "298"},
    {Rider: "ROSSI Valentino", Nation: "ITA", Points: "249"},
    {Rider: "LORENZO Jorge", Nation: "SPA", Points: "233"},
];
const columns = {
Rider: "Rider",
Nation: "Nation",
Points: "Points",
};
csv.stringify(input, { header: true, columns: columns }, function(err, output){
    console.log(output);
});

実行結果

MARQUEZ Marc,SPA,298
ROSSI Valentino,ITA,249
LORENZO Jorge,SPA,233

headerオプションを指定すると、プロパティ名が列名になります。

import * as csv from "csv";

const input = [
    {Rider: "MARQUEZ Marc", Nation: "SPA", Points: "298"},
    {Rider: "ROSSI Valentino", Nation: "ITA", Points: "249"},
    {Rider: "LORENZO Jorge", Nation: "SPA", Points: "233"},
];
csv.stringify(input, { header: true}, function(err, output){
    console.log(output);
});

実行結果

Rider,Nation,Points
MARQUEZ Marc,SPA,298
ROSSI Valentino,ITA,249
LORENZO Jorge,SPA,233

列名を設定したいときは、columnsオプションを使用します。

import * as csv from "csv";

const input = [
    {Rider: "MARQUEZ Marc", Nation: "SPA", Points: "298"},
    {Rider: "ROSSI Valentino", Nation: "ITA", Points: "249"},
    {Rider: "LORENZO Jorge", Nation: "SPA", Points: "233"},
];
const columns = {
Rider: "選手",
Nation: "国",
Points: "ポイント",
};
csv.stringify(input, { header: true, columns: columns}, function(err, output){
    console.log(output);
});

実行結果

選手,国,ポイント
MARQUEZ Marc,SPA,298
ROSSI Valentino,ITA,249
LORENZO Jorge,SPA,233

delimiterオプション

delimiterオプションで区切り文字を変更できます。

次の例ではタブ区切りで出力します。

import * as csv from "csv";

const input = [ [ "1", "2", "3", "4" ], [ "a", "b", "c", "d" ] ];
csv.stringify(input, {delimiter: "\t"}, function(err, output){
    console.log(output);
});

Visual Studioでstd::min()/std::max()がエラーになるときの回避策

C++11からstd::minとstd::maxは複数の引数を受け取ることができるようになりました。

#include <algorithm>

int i = std::min({1, 2, 3, 4});

ところが、Visual Studioで上記のコードを実行するとコンパイルエラーになります。

原因は、windows.hにmin/maxのマクロが定義されているため。

マイクロソフトのページにある解決策は、「NOMINMAXのプリプロセッサシンボルを定義します。」とあります。
しかし、それができない状況もあります。

他の回避策が「windows.hのmin/maxマクロ回避策4パターン – yohhoyの日記」に紹介されていました。

(std::min)(…)や(std::max)(…)のように、括弧で囲みマクロ展開を抑止することで問題を回避できます。

#include <algorithm>

int i = (std::min)({1, 2, 3, 4});

これでエラーにならなくなりました。