30. ES6の新機能の概要
目次
本書へのご支援をお願いします: 購入(PDF、EPUB、MOBI) または 寄付
(広告、ブロックしないでください。)

30. ES6の新機能の概要

この章では、本書のすべての章の概要セクションをまとめています。



30.1 ES6の機能のカテゴリ

ES6仕様の導入部には、すべての新機能がリストされています。

[ECMAScript 6]の主な拡張機能には、モジュール、クラス宣言、レキシカルブロックスコープ、イテレータとジェネレーター、非同期プログラミングのためのPromise、分割代入パターン、適切な末尾呼び出しが含まれます。組み込みのECMAScriptライブラリは、マップ、セット、およびバイナリ数値の配列を含む追加のデータ抽象化をサポートするために拡張されており、文字列と正規表現のUnicode補助文字の追加サポートも提供されています。組み込みは、サブクラス化を介して拡張可能になりました。

機能には3つの主要なカテゴリがあります。

30.2 新しい数値およびMath機能

30.2.1 新しい整数リテラル

バイナリ表記と8進数表記で整数を指定できるようになりました。

> 0xFF // ES5: hexadecimal
255
> 0b11 // ES6: binary
3
> 0o10 // ES6: octal
8

30.2.2 新しいNumberプロパティ

グローバルオブジェクトNumberにいくつかの新しいプロパティが追加されました。

30.2.3 新しいMathメソッド

グローバルオブジェクトMathには、数値演算、三角関数、ビット単位演算のための新しいメソッドがあります。4つの例を見てみましょう。

Math.sign()は数値の符号を返します。

> Math.sign(-8)
-1
> Math.sign(0)
0
> Math.sign(3)
1

Math.trunc()は数値の小数部を削除します。

> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3

Math.log10()は、底が10の対数を計算します。

> Math.log10(100)
2

Math.hypot()は、引数の2乗の合計の平方根(ピタゴラスの定理)を計算します。

> Math.hypot(3, 4)
5    

30.3 新しい文字列機能

新しい文字列メソッド

> 'hello'.startsWith('hell')
true
> 'hello'.endsWith('ello')
true
> 'hello'.includes('ell')
true
> 'doo '.repeat(3)
'doo doo doo '

ES6には、新しい種類の文字列リテラルであるテンプレートリテラルがあります。

// String interpolation via template literals (in backticks)
const first = 'Jane';
const last = 'Doe';
console.log(`Hello ${first} ${last}!`);
    // Hello Jane Doe!

// Template literals also let you create strings with multiple lines
const multiLine = `
This is
a string
with multiple
lines`;

30.4 シンボル

シンボルは、ECMAScript 6の新しいプリミティブ型です。ファクトリ関数を介して作成されます。

const mySymbol = Symbol('mySymbol');

ファクトリ関数を呼び出すたびに、新しく一意のシンボルが作成されます。オプションのパラメータは、シンボルを印刷するときに表示される説明的な文字列です(他の目的はありません)。

> mySymbol
Symbol(mySymbol)

30.4.1 ユースケース1:一意のプロパティキー

シンボルは主に一意のプロパティキーとして使用されます。シンボルは、他のプロパティキー(シンボルまたは文字列)と衝突することはありません。たとえば、Symbol.iteratorに格納されているシンボルをメソッドのキーとして使用することにより、オブジェクトをイテラブルfor-ofループおよびその他の言語メカニズムを介して使用可能)にすることができます(イテラブルの詳細については、イテレーションの章で説明します)。

const iterableObject = {
    [Symbol.iterator]() { // (A)
        ···
    }
}
for (const x of iterableObject) {
    console.log(x);
}
// Output:
// hello
// world

A行では、シンボルがメソッドのキーとして使用されています。この一意のマーカーにより、オブジェクトはイテラブルになり、for-ofループを使用できるようになります。

30.4.2 ユースケース2:概念を表す定数

ECMAScript 5では、色などの概念を表すために文字列を使用していた可能性があります。ES6では、シンボルを使用すると、常に一意であることが保証されます。

const COLOR_RED    = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN  = Symbol('Green');
const COLOR_BLUE   = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');

function getComplement(color) {
    switch (color) {
        case COLOR_RED:
            return COLOR_GREEN;
        case COLOR_ORANGE:
            return COLOR_BLUE;
        case COLOR_YELLOW:
            return COLOR_VIOLET;
        case COLOR_GREEN:
            return COLOR_RED;
        case COLOR_BLUE:
            return COLOR_ORANGE;
        case COLOR_VIOLET:
            return COLOR_YELLOW;
        default:
            throw new Exception('Unknown color: '+color);
    }
}

Symbol('Red')を呼び出すたびに、新しいシンボルが作成されます。したがって、COLOR_REDを別の値と間違えることはありません。それが文字列'Red'であった場合とは異なります。

30.4.3 落とし穴:シンボルを文字列に強制変換できない

シンボルを文字列に強制変換(暗黙的に変換)すると、例外がスローされます。

const sym = Symbol('desc');

const str1 = '' + sym; // TypeError
const str2 = `${sym}`; // TypeError

唯一の解決策は、明示的に変換することです。

const str2 = String(sym); // 'Symbol(desc)'
const str3 = sym.toString(); // 'Symbol(desc)'

強制変換を禁止すると、一部のエラーが防止されますが、シンボルの操作も複雑になります。

次の操作は、プロパティキーとしてシンボルを認識します。

次の操作は、プロパティキーとしてシンボルを無視します。

30.5 テンプレートリテラル

ES6には、テンプレートリテラルタグ付きテンプレートリテラルの2つの新しい種類のリテラルがあります。これらの2つのリテラルは似たような名前で見た目も似ていますが、まったく異なります。したがって、区別することが重要です。

テンプレートリテラルは、複数行にまたがり、補間式(${···}を介して挿入)を含むことができる文字列リテラルです。

const firstName = 'Jane';
console.log(`Hello ${firstName}!
How are you
today?`);

// Output:
// Hello Jane!
// How are you
// today?

タグ付きテンプレートリテラル(略してタグ付きテンプレート)は、テンプレートリテラルの前に関数を記述することによって作成されます。

> String.raw`A \tagged\ template`
'A \\tagged\\ template'

タグ付きテンプレートは関数呼び出しです。前の例では、タグ付きテンプレートの結果を生成するために、メソッドString.rawが呼び出されます。

30.6 変数とスコープ

ES6では、変数を宣言する2つの新しい方法であるletconstが提供されます。これらは主に、ES5での変数の宣言方法であるvarに代わるものです。

30.6.1 let

letvar と同様に動作しますが、宣言された変数がブロックスコープを持つ点が異なります。つまり、現在のブロック内でのみ存在します。一方、var関数スコープです。

次のコードでは、let で宣言された変数 tmp が、A行から始まるブロック内でのみ存在することがわかります。

function order(x, y) {
    if (x > y) { // (A)
        let tmp = x;
        x = y;
        y = tmp;
    }
    console.log(tmp===x); // ReferenceError: tmp is not defined
    return [x, y];
}

30.6.2 const

constlet と同様に動作しますが、宣言する変数はすぐに初期化する必要があり、その後値を変更することはできません。

const foo;
    // SyntaxError: missing = in const declaration

const bar = 123;
bar = 456;
    // TypeError: `bar` is read-only

for-of はループの反復ごとに1つのバインディング(変数のためのストレージスペース)を作成するため、ループ変数を const で宣言しても問題ありません。

for (const x of ['a', 'b']) {
    console.log(x);
}
// Output:
// a
// b

30.6.3 変数の宣言方法

次の表は、ES6で変数を宣言できる6つの方法の概要を示しています(kangax氏による表に触発されました)。

  ホイスティング スコープ グローバルプロパティを作成するか
var 宣言 関数 はい
let 一時的なデッドゾーン ブロック いいえ
const 一時的なデッドゾーン ブロック いいえ
関数 完全 ブロック はい
class いいえ ブロック いいえ
import 完全 モジュールグローバル いいえ

30.7 分割代入

分割代入 は、(ネストされている可能性のある)オブジェクトや配列に格納されたデータから複数の値を抽出する便利な方法です。データの受け渡し場所(代入の左辺など)で使用できます。値の抽出方法はパターンによって指定されます(例については以下を参照)。

30.7.1 オブジェクトの分割代入

オブジェクトの分割代入

const obj = { first: 'Jane', last: 'Doe' };
const {first: f, last: l} = obj;
    // f = 'Jane'; l = 'Doe'

// {prop} is short for {prop: prop}
const {first, last} = obj;
    // first = 'Jane'; last = 'Doe'

分割代入は戻り値の処理に役立ちます

const obj = { foo: 123 };

const {writable, configurable} =
    Object.getOwnPropertyDescriptor(obj, 'foo');

console.log(writable, configurable); // true true

30.7.2 配列の分割代入

配列の分割代入(すべての反復可能な値に対して機能します)

const iterable = ['a', 'b'];
const [x, y] = iterable;
    // x = 'a'; y = 'b'

分割代入は戻り値の処理に役立ちます

const [all, year, month, day] =
    /^(\d\d\d\d)-(\d\d)-(\d\d)$/
    .exec('2999-12-31');

30.7.3 分割代入はどこで使用できますか?

分割代入は以下の場所で使用できます(デモンストレーションのために配列パターンを示しています。オブジェクトパターンも同様に機能します)。

// Variable declarations:
const [x] = ['a'];
let [x] = ['a'];
var [x] = ['a'];

// Assignments:
[x] = ['a'];

// Parameter definitions:
function f([x]) { ··· }
f(['a']);

for-of ループ内でも分割代入を使用できます

const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
    console.log(index, element);
}
// Output:
// 0 a
// 1 b

30.8 パラメータ処理

ECMAScript 6では、パラメータ処理が大幅にアップグレードされました。パラメータのデフォルト値、レストパラメータ(可変長引数)、分割代入がサポートされるようになりました。

さらに、スプレッド演算子は、関数/メソッド/コンストラクタ呼び出しと配列リテラルで役立ちます。

30.8.1 デフォルトパラメータ値

デフォルトパラメータ値は、等号(=)を介してパラメータに指定されます。呼び出し元がパラメータの値を提供しない場合、デフォルト値が使用されます。次の例では、y のデフォルトパラメータ値は0です。

function func(x, y=0) {
    return [x, y];
}
func(1, 2); // [1, 2]
func(1); // [1, 0]
func(); // [undefined, 0]

30.8.2 レストパラメータ

パラメータ名の前にレスト演算子(...)を付けると、そのパラメータは配列を介して残りのすべてのパラメータを受け取ります。

function format(pattern, ...params) {
    return {pattern, params};
}
format(1, 2, 3);
    // { pattern: 1, params: [ 2, 3 ] }
format();
    // { pattern: undefined, params: [] }

30.8.3 分割代入による名前付きパラメータ

パラメータリストでオブジェクトパターンを使用して分割代入すると、名前付きパラメータをシミュレートできます。

function selectEntries({ start=0, end=-1, step=1 } = {}) { // (A)
    // The object pattern is an abbreviation of:
    // { start: start=0, end: end=-1, step: step=1 }

    // Use the variables `start`, `end` and `step` here
    ···
}

selectEntries({ start: 10, end: 30, step: 2 });
selectEntries({ step: 3 });
selectEntries({});
selectEntries();

A行の = {} を使用すると、パラメータなしで selectEntries() を呼び出すことができます。

30.8.4 スプレッド演算子 (...)

関数とコンストラクタの呼び出しでは、スプレッド演算子は反復可能な値を引数に変換します。

> Math.max(-1, 5, 11, 3)
11
> Math.max(...[-1, 5, 11, 3])
11
> Math.max(-1, ...[-5, 11], 3)
11

配列リテラルでは、スプレッド演算子は反復可能な値を配列要素に変換します。

> [1, ...[2,3], 4]
[1, 2, 3, 4]

30.9 ECMAScript 6の呼び出し可能なエンティティ

ES5では、単一の構成要素である(従来の)関数が3つの役割を果たしていました。

ES6では、より特化が進んでいます。3つの役割は、現在次のように処理されています。関数の定義とクラスの定義に関する限り、定義は宣言または式になります。

特にコールバックの場合、アロー関数は周囲のスコープの this を隠蔽しないため便利です。

長いコールバックやスタンドアロン関数の場合、従来の関数でも問題ありません。一部のAPIでは、暗黙のパラメータとして this を使用します。その場合、従来の関数を使用するしかありません。

私は以下を区別することに注意してください。

(後で説明するように)動作が異なるにもかかわらず、これらのエンティティはすべて関数です。例えば

> typeof (() => {}) // arrow function
'function'
> typeof function* () {} // generator function
'function'
> typeof class {} // class
'function'

30.10 アロー関数

アロー関数には2つの利点があります。

1つ目は、従来の関数式よりも冗長でないことです。

const arr = [1, 2, 3];
const squares = arr.map(x => x * x);

// Traditional function expression:
const squares = arr.map(function (x) { return x * x });

2つ目は、その this が周囲(レキシカル)から取得されることです。したがって、bind()that = this はもう必要ありません。

function UiComponent() {
    const button = document.getElementById('myButton');
    button.addEventListener('click', () => {
        console.log('CLICK');
        this.handleClick(); // lexical `this`
    });
}

次の変数はすべてアロー関数内でレキシカルです。

30.11 クラス以外の新しいOOP機能

30.11.1 新しいオブジェクトリテラル機能

メソッド定義

const obj = {
    myMethod(x, y) {
        ···
    }
};

プロパティ値の短縮形

const first = 'Jane';
const last = 'Doe';

const obj = { first, last };
// Same as:
const obj = { first: first, last: last };

算出されたプロパティキー

const propKey = 'foo';
const obj = {
    [propKey]: true,
    ['b'+'ar']: 123
};

この新しい構文は、メソッド定義にも使用できます

const obj = {
    ['h'+'ello']() {
        return 'hi';
    }
};
console.log(obj.hello()); // hi

算出されたプロパティキーの主な使用例は、シンボルをプロパティキーとして簡単に使用できるようにすることです。

30.11.2 Object の新しいメソッド

Object の最も重要な新しいメソッドは assign() です。従来、この機能はJavaScriptの世界では extend() と呼ばれていました。この従来の操作とは対照的に、Object.assign()独自の(継承されていない)プロパティのみを考慮します。

const obj = { foo: 123 };
Object.assign(obj, { bar: true });
console.log(JSON.stringify(obj));
    // {"foo":123,"bar":true}

30.12 クラス

クラスとサブクラス

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
        return `(${this.x}, ${this.y})`;
    }
}

class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y);
        this.color = color;
    }
    toString() {
        return super.toString() + ' in ' + this.color;
    }
}

クラスの使用

> const cp = new ColorPoint(25, 8, 'green');

> cp.toString();
'(25, 8) in green'

> cp instanceof ColorPoint
true
> cp instanceof Point
true

内部的には、ES6クラスは根本的に新しいものではありません。主に、古いスタイルのコンストラクタ関数を作成するためのより便利な構文を提供します。typeof を使用すると、それが確認できます。

> typeof Point
'function'

30.13 モジュール

JavaScriptには長い間モジュールがありましたが、言語に組み込まれたものではなく、ライブラリを介して実装されていました。ES6は、JavaScriptに組み込みモジュールが搭載された初めてのバージョンです。

ES6モジュールはファイルに格納されます。ファイルごとに1つのモジュールがあり、モジュールごとに1つのファイルがあります。モジュールからものをエクスポートする方法は2つあります。これら2つの方法は混合できますが、通常は別々に使用することをお勧めします。

30.13.1 複数の名前付きエクスポート

複数の名前付きエクスポートが可能です。

//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}

//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

モジュール全体をインポートすることもできます。

//------ main.js ------
import * as lib from 'lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5

30.13.2 単一のデフォルトエクスポート

単一のデフォルトエクスポートが可能です。例えば、関数

//------ myFunc.js ------
export default function () { ··· } // no semicolon!

//------ main1.js ------
import myFunc from 'myFunc';
myFunc();

またはクラス

//------ MyClass.js ------
export default class { ··· } // no semicolon!

//------ main2.js ------
import MyClass from 'MyClass';
const inst = new MyClass();

関数またはクラス(匿名宣言)をデフォルトでエクスポートする場合、末尾にセミコロンがないことに注意してください。

30.13.3 ブラウザ:スクリプトとモジュール

  スクリプト モジュール
HTML要素 <script> <script type="module">
デフォルトモード 非strict strict
トップレベルの変数は グローバル モジュールに対してローカル
トップレベルでのthisの値 window undefined
実行 同期的に 非同期的に
宣言的なインポート(importステートメント) いいえ はい
プログラム的なインポート(PromiseベースのAPI) はい はい
ファイル拡張子 .js .js

30.14 for-of ループ

for-of は、ES6の新しいループで、for-inforEach() の両方を置き換え、新しいイテレーションプロトコルをサポートします。

反復可能なオブジェクト(配列、文字列、マップ、セットなど。チャプター「反復可能オブジェクトとイテレーター」を参照)をループ処理するために使用します

const iterable = ['a', 'b'];
for (const x of iterable) {
    console.log(x);
}

// Output:
// a
// b

breakcontinuefor-of ループ内で機能します。

for (const x of ['a', '', 'b']) {
    if (x.length === 0) break;
    console.log(x);
}

// Output:
// a

配列をループ処理中に、要素とそのインデックスの両方にアクセスします(of の前の角かっこは、分割代入を使用していることを意味します)。

const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
    console.log(`${index}. ${element}`);
}

// Output:
// 0. a
// 1. b

マップ内の [キー、値] エントリをループ処理します(of の前の角かっこは、分割代入を使用していることを意味します)。

const map = new Map([
    [false, 'no'],
    [true, 'yes'],
]);
for (const [key, value] of map) {
    console.log(`${key} => ${value}`);
}

// Output:
// false => no
// true => yes

30.15 新しい配列機能

新しい静的 Array メソッド

新しい Array.prototype メソッド

30.16 マップとセット

特に、ECMAScript 6では、MapWeakMapSetWeakSetの4つのデータ構造が新しく追加されました。

30.16.1 マップ

マップのキーには任意の値を指定できます

> const map = new Map(); // create an empty Map
> const KEY = {};

> map.set(KEY, 123);
> map.get(KEY)
123
> map.has(KEY)
true
> map.delete(KEY);
true
> map.has(KEY)
false

マップの初期データを設定するために、[キー、値]ペアの配列(または任意の反復可能オブジェクト)を使用できます

const map = new Map([
    [ 1, 'one' ],
    [ 2, 'two' ],
    [ 3, 'three' ], // trailing comma is ignored
]);

30.16.2 セット

セットは一意の要素のコレクションです

const arr = [5, 1, 5, 7, 7, 5];
const unique = [...new Set(arr)]; // [ 5, 1, 7 ]

ご覧のとおり、コンストラクタにこれらの要素に対する反復可能オブジェクト(例の arr)を渡すと、要素を使用してセットを初期化できます。

30.16.3 WeakMap

WeakMapは、キーがガベージコレクションされるのを妨げないマップです。つまり、メモリリークを心配することなく、オブジェクトにデータを関連付けることができます。例:

//----- Manage listeners

const _objToListeners = new WeakMap();

function addListener(obj, listener) {
    if (! _objToListeners.has(obj)) {
        _objToListeners.set(obj, new Set());
    }
    _objToListeners.get(obj).add(listener);
}

function triggerListeners(obj) {
    const listeners = _objToListeners.get(obj);
    if (listeners) {
        for (const listener of listeners) {
            listener();
        }
    }
}

//----- Example: attach listeners to an object

const obj = {};
addListener(obj, () => console.log('hello'));
addListener(obj, () => console.log('world'));

//----- Example: trigger listeners

triggerListeners(obj);

// Output:
// hello
// world

30.17 型付き配列

型付き配列は、バイナリデータを処理するためのECMAScript 6 APIです。

コード例

const typedArray = new Uint8Array([0,1,2]);
console.log(typedArray.length); // 3
typedArray[0] = 5;
const normalArray = [...typedArray]; // [5,1,2]

// The elements are stored in typedArray.buffer.
// Get a different view on the same data:
const dataView = new DataView(typedArray.buffer);
console.log(dataView.getUint8(0)); // 5

ArrayBuffer のインスタンスは、処理されるバイナリデータを格納します。データをアクセスするために2種類のビューが使用されます。

次のブラウザAPIは型付き配列をサポートしています(詳細については、専用のセクションで説明します)。

30.18 反復可能オブジェクトとイテレーター

ES6 では、データを走査するための新しいメカニズムとしてイテレーションが導入されました。イテレーションには、2 つの中心的な概念があります。

TypeScript の表記法でインターフェースとして表現すると、これらの役割は次のようになります。

interface Iterable {
    [Symbol.iterator]() : Iterator;
}
interface Iterator {
    next() : IteratorResult;
}
interface IteratorResult {
    value: any;
    done: boolean;
}

30.18.1 イテラブルな値

以下の値はイテラブルです。

プレーンオブジェクトはイテラブルではありません(その理由は専用のセクションで説明します)。

30.18.2 イテレーションをサポートする構文

イテレーションを通してデータにアクセスする言語構文:

30.19 ジェネレーター

30.19.1 ジェネレーターとは?

ジェネレーターは、一時停止と再開が可能なプロセス(コードの一部)と考えることができます。

function* genFunc() {
    // (A)
    console.log('First');
    yield;
    console.log('Second');
}

新しい構文に注目してください:function* は、ジェネレーター関数のための新しい「キーワード」です(ジェネレーターメソッドもあります)。yield は、ジェネレーターが自身を一時停止させることができる演算子です。さらに、ジェネレーターは yield を介して入力を受け取り、出力を送信することもできます。

ジェネレーター関数 genFunc() を呼び出すと、プロセスを制御するために使用できるジェネレーターオブジェクト genObj が得られます。

const genObj = genFunc();

プロセスは、最初は A 行で一時停止しています。genObj.next() は実行を再開し、genFunc() 内の yield が実行を一時停止します。

genObj.next();
// Output: First
genObj.next();
// output: Second

30.19.2 ジェネレーターの種類

ジェネレーターには 4 つの種類があります。

  1. ジェネレーター関数宣言
     function* genFunc() { ··· }
     const genObj = genFunc();
    
  2. ジェネレーター関数式
     const genFunc = function* () { ··· };
     const genObj = genFunc();
    
  3. オブジェクトリテラル内のジェネレーターメソッド定義
     const obj = {
         * generatorMethod() {
             ···
         }
     };
     const genObj = obj.generatorMethod();
    
  4. クラス定義(クラス宣言またはクラス式)内のジェネレーターメソッド定義
     class MyClass {
         * generatorMethod() {
             ···
         }
     }
     const myInst = new MyClass();
     const genObj = myInst.generatorMethod();
    

30.19.3 ユースケース:イテラブルの実装

ジェネレーターによって返されるオブジェクトはイテラブルです。各 yield は、イテレートされた値のシーケンスに寄与します。したがって、ジェネレーターを使用してイテラブルを実装し、それをさまざまな ES6 言語メカニズム (for-of ループ、スプレッド演算子 (...) など) で利用できます。

次の関数は、オブジェクトのプロパティを反復処理し、各プロパティごとに [キー、値] のペアを返します。

function* objectEntries(obj) {
    const propKeys = Reflect.ownKeys(obj);

    for (const propKey of propKeys) {
        // `yield` returns a value and then pauses
        // the generator. Later, execution continues
        // where it was previously paused.
        yield [propKey, obj[propKey]];
    }
}

objectEntries() はこのように使用します。

const jane = { first: 'Jane', last: 'Doe' };
for (const [key,value] of objectEntries(jane)) {
    console.log(`${key}: ${value}`);
}
// Output:
// first: Jane
// last: Doe

objectEntries() が正確にどのように機能するかは、専用のセクションで説明します。ジェネレーターを使用せずに同じ機能を実装するには、多くの作業が必要です。

30.19.4 ユースケース:よりシンプルな非同期コード

ジェネレーターを使用すると、Promise の操作を大幅に簡略化できます。Promise ベースの関数 fetchJson() と、ジェネレーターを使用してどのように改善できるかを見てみましょう。

function fetchJson(url) {
    return fetch(url)
    .then(request => request.text())
    .then(text => {
        return JSON.parse(text);
    })
    .catch(error => {
        console.log(`ERROR: ${error.stack}`);
    });
}

ライブラリ co とジェネレーターを使用すると、この非同期コードは同期的に見えます。

const fetchJson = co.wrap(function* (url) {
    try {
        let request = yield fetch(url);
        let text = yield request.text();
        return JSON.parse(text);
    }
    catch (error) {
        console.log(`ERROR: ${error.stack}`);
    }
});

ECMAScript 2017 では、内部的にジェネレーターに基づいた async 関数が導入されます。async 関数を使用すると、コードは次のようになります。

async function fetchJson(url) {
    try {
        let request = await fetch(url);
        let text = await request.text();
        return JSON.parse(text);
    }
    catch (error) {
        console.log(`ERROR: ${error.stack}`);
    }
}

すべてのバージョンは次のように呼び出すことができます。

fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));

30.19.5 ユースケース:非同期データの受信

ジェネレーターは、yield を介して next() から入力を受け取ることができます。つまり、新しいデータが非同期的に到着するたびにジェネレーターを起動することができ、ジェネレーターにとっては同期的にデータを受け取っているように感じられます。

30.20 新しい正規表現機能

以下の正規表現機能は、ECMAScript 6 で新たに追加されたものです。

30.21 非同期プログラミングのための Promise

Promise は、非同期計算の結果を配信するためのコールバックの代替手段です。非同期関数の実装者にはより多くの労力がかかりますが、それらの関数のユーザーにはいくつかの利点があります。

次の関数は、Promise を介して非同期的に結果を返します。

function asyncFunc() {
    return new Promise(
        function (resolve, reject) {
            ···
            resolve(result);
            ···
            reject(error);
        });
}

asyncFunc() は次のように呼び出します。

asyncFunc()
.then(result => { ··· })
.catch(error => { ··· });

30.21.1 then() 呼び出しの連鎖

then() は常に Promise を返すため、メソッド呼び出しを連鎖させることができます。

asyncFunc1()
.then(result1 => {
    // Use result1
    return asyncFunction2(); // (A)
})
.then(result2 => { // (B)
    // Use result2
})
.catch(error => {
    // Handle errors of asyncFunc1() and asyncFunc2()
});

then() によって返される Promise P がどのように解決されるかは、そのコールバックが何を行うかによって決まります。

さらに、catch() が 2 つの非同期関数呼び出し (asyncFunction1()asyncFunction2()) のエラーをどのように処理するかに注意してください。つまり、キャッチされないエラーは、エラーハンドラーが存在するまで渡されます。

30.21.2 非同期関数の並列実行

then() を介して非同期関数呼び出しを連鎖させると、それらは順番に 1 つずつ実行されます。

asyncFunc1()
.then(() => asyncFunc2());

そうせずに、すべてをすぐに呼び出すと、それらは基本的に並行して実行されます (Unix プロセスの用語で言うと *fork* )。

asyncFunc1();
asyncFunc2();

Promise.all() を使用すると、すべての結果が揃ったときに通知を受け取ることができます (Unix プロセスの用語で言うと *join* )。その入力は Promise の配列で、その出力は、結果の配列で満たされた単一の Promise です。

Promise.all([
    asyncFunc1(),
    asyncFunc2(),
])
.then(([result1, result2]) => {
    ···
})
.catch(err => {
    // Receives first rejection among the Promises
    ···
});

30.21.3 用語集:Promise

Promise API は、結果を非同期的に配信するためのものです。Promise オブジェクト (略して Promise) は、そのオブジェクトを介して配信される結果の代わりとなるものです。

状態

状態の変化への反応

状態の変更:Promise の状態を変更するための 2 つの操作があります。どちらかを一度呼び出すと、それ以降の呼び出しは効果がありません。

30.22 プロキシを使用したメタプログラミング

プロキシを使用すると、オブジェクトに対して実行される操作 (プロパティの取得など) をインターセプトしてカスタマイズできます。これらは、メタプログラミング機能です。

次の例では、proxy は操作をインターセプトしているオブジェクトであり、handler はインターセプトを処理するオブジェクトです。この場合、単一の操作 get (プロパティの取得) のみをインターセプトしています。

const target = {};
const handler = {
    get(target, propKey, receiver) {
        console.log('get ' + propKey);
        return 123;
    }
};
const proxy = new Proxy(target, handler);

プロパティ proxy.foo を取得すると、ハンドラーはその操作をインターセプトします。

> proxy.foo
get foo
123

インターセプトできる操作のリストについては、完全な API のリファレンスを参照してください。

次へ:注釈