js6 クラス Promise, async/await
問題一覧
1
クラス全体にメソッドを割り当てることもできます。このようなメソッドは static(静的) と呼ばれます。 クラス宣言の中では、次のように static キーワードを付けます。 class User { static staticMethod() { alert(this === User); } } User.staticMethod(); // true
2
通常 static メソッドは、クラスには属するが、特定のオブジェクトには属さない関数を実装するのに使用されます。 1. 比較用のメソッド 例えば、Article オブジェクトがあり、それらを比較するための関数が必要とします。 自然な解決策は、静的メソッド Article.compare を追加することです。: class Article { constructor(title, date) { this.title = title; this.date = date; } static compare(articleA, articleB) { return articleA.date - articleB.date; } } // usage let articles = [ new Article("Mind", new Date(2019, 1, 1)), new Article("Body", new Date(2019, 0, 1)), new Article("JavaScript", new Date(2019, 11, 1)) ]; articles.sort(Article.compare); alert( articles[0].title ); // Body ここでは、Article.compare は記事を比較する手段として、記事の “上位” にいます。記事のメソッドと言うよりはクラス全体のメソッドです。 2 ファクトリーメソッド 別の例は、いわゆる “ファクトリー” メソッドです。 記事の作成には複数の方法が必要です: 与えられたパラメータ(title, date など)による作成 今日の日付の空の記事の作成 … 最初の方法はコンストラクタで実装することができます。そして、2 つ目の方法としてはクラスの静的メソッドを作ることができます。 ここでの Article.createTodays() を見てください: class Article { constructor(title, date) { this.title = title; this.date = date; } static createTodays() { // 思い出してください, this = Article return new this("Today's digest", new Date()); } } let article = Article.createTodays(); alert( article.title ); // Todays digest これで今日のダイジェストを作成する必要があるたびに、Article.createTodays() を呼べます。 3 データベース関連クラスの操作メソッド 静的メソッドは、次のように、データベース関連のクラスでデータベースの検索/保存/削除のためにも使用されます。: // Article は記事を管理するための特別なクラスと仮定します // 記事を削除するための static メソッド: Article.remove({ id: 12345 });
3
静的プロパティも可能で、通常のクラスプロパティと同じように見えますが、先頭に static が付きます。 class Article { static publisher = "Ilya Kantor"; } alert(Article.publisher); // Ilya Kantor
4
プロトタイプチェーンが形成されるので、継承先のクラスでも静的プロパティやメソッドを継承し、使えるようになる。 class Animal { static planet = "Earth"; constructor(name, speed) { this.speed = speed; this.name = name; } run(speed = 0) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } static compare(animalA, animalB) { return animalA.speed - animalB.speed; } } // Inherit from Animal class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } } let rabbits = [ new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5) ]; rabbits.sort(Rabbit.compare); rabbits[0].run(); // Black Rabbit runs with speed 5. alert(Rabbit.planet); // Earth
5
クラス側の解説 extendすることで、 Rabbitクラス(関数ともいう)の[[prototype]]がAnimalクラス(関数)になる。 extendsはコンストラクタ関数間のプロトタイプチェーンの掲載 (つまるところ関数オブジェクトのプロパティの継承) とプロトタイプ間のチェーン形成 インスタンス→Rabbit.prototype → Animal.prototype を行う class Animal {} class Rabbit extends Animal {} // 静的 alert(Rabbit.__proto__ === Animal); // true // 通常のメソッド alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
6
内部インタフェース: クラスの他のメソッドからアクセス可能だが、外側からはアクセスできないメソッドやプロパティ。 外部インタフェース: 外部のクラスからもアクセス可能なメソッドやプロパティ。 コーヒーメーカーで例えるなら、内部に隠されているもの: ボイラーチューブや発熱体など、は内部インタフェースです。 内部インタフェースはオブジェクトが機能するために使われ、その構成要素はお互いに使用されます。例えば、ボイラーチューブは発熱体に取り付けられます。 しかし、コーヒーメーカーの外側からは、誰もそこに届かないよう保護カバーで閉ざされています。 内部の詳細は隠されており、アクセスできません。私たちは、外部インタフェースを介してのみその機能を利用できます。
7
外部インターフェース 内部でどのように動いているか完全に分からないかもしれませんが、問題ありません
8
パブリック(public): どこからでもアクセス可能です。これらは外部インタフェースになります。 今まで、私たちはパブリックなプロパティとメソッドのみを使用していました。 プライベート(private): クラス内部からのみアクセスできます。これらは内部インタフェース用です。 プロテクト(protected)” : これは、クラス及び、そのクラスを継承したサブクラスの内部からのみアクセス可能であることを意味します。これも内部インタフェースには役立ちます。通常は、継承しているクラスを適切に拡張できるよう、それらにアクセスさせたいため、ある意味ではプライベートよりも広く知られています。 ※ちなみにJavaScriptでは実装されてないので、言語レベルで制御はされず、運用としての実装になる。。
9
_をつける。 class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { if (value < 0) { value = 0; } this._waterAmount = value; } get waterAmount() { return this._waterAmount; } constructor(power) { this._power = power; } } // コーヒーメーカーを生成 let coffeeMachine = new CoffeeMachine(100); // 水を追加 coffeeMachine.waterAmount = -10; // _waterAmount は -10 ではなく、 0 になります
10
セッターはなしで、ゲッターだけ利用するようにすれば読み取り専用になる。 例:powerプロパティが読み取り専用 class CoffeeMachine { // ... constructor(power) { this._power = power; } get power() { return this._power; } } // コーヒーメーカーを作成 let coffeeMachine = new CoffeeMachine(100); alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W coffeeMachine.power = 25; // Error (setter はないので) プロパティフラグとかでも同じことできるけどこっちのほうがらく
11
100が出力される。 protectedは言語レベルでサポートされてない。 powerプロパティで操作してね!お願い!ってことやね。
12
プライベートは # から始める必要があります。それらはクラス内部からのみアクセス可能です。 例えば、ここではプライベートな #waterLimit プロパティを追加し、水量をチェックするロジックを別のメソッドに抜き出しています: また、メソッドもプライベートにしている。 class CoffeeMachine { #waterLimit = 200; #fixWaterAmount(value) { if (value < 0) return 0; if (value > this.#waterLimit) return this.#waterLimit; } setWaterAmount(value) { this.#waterLimit = this.#fixWaterAmount(value); } } let coffeeMachine = new CoffeeMachine(); // can't access privates from outside of the class coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error ちなみにクロージャとかweakmapを使うと プライベートなプロパティを実装できるっぽいけど上記の方が楽!!
13
なにもしなければパブリック。 プライベートフィールドはパブリックなものと衝突しません。 プライベートな #waterAmount とパブリックな waterAmount フィールド両方を同時にもつことができます。 例えば、#waterAmount のアクセサとなる waterAmount を作りましょう。: class CoffeeMachine { #waterAmount = 0; get waterAmount() { return this.#waterAmount; } set waterAmount(value) { if (value < 0) value = 0; this.#waterAmount = value; } } let machine = new CoffeeMachine(); machine.waterAmount = 100; alert(machine.#waterAmount); // Error
14
プライベートは継承先でも使えない プロテクテドならいける。
15
protected privateは新しめの機能なので、ブラウザによってはサポートされてなかったりするし、 継承先で使えないという制限は厳しすぎなので、protectedがちょうどいい。
16
this['#name'] は期待通り動作しません。 試したらundefined がかえってきた。 this.#nameはいける。 Private フィールドは特別です。 これは private であることを維持するための、構文上の制限になります。 外側から使われないから内部で気をつけるだけだけど 文字列でアクセスできないのはどうなんだろな Object.entries(obj)とかでプロパティ情報とってきて以下のようにクローンしようとしてもプライベートなプロパティはコピーできないかも? obj[key] = value; いやそもそも外部からアクセスできないか
17
カプセル化
18
1 外部から変更されずらくする。 外部からの変更を意図していないものが勝手に変更された場合、結果は予測不可能です。 2 利用者へ通知せず、内部のコード変更が容易 もし内部インタフェースを厳密に区切ると、クラスの開発者は利用者へ通知しなくても内部のプロパティとメソッドを自由に変更することができます。 3 ドキュメント化しやすい。 実装の詳細が隠されていて、シンプルかつ良くドキュメント化された外部インタフェースが利用可能であることは常に便利です。
19
戻り値のインスタンスのクラスはpowerArrayとなる。 Arrayのメソッドなのに、Arrayのインスタンスがかえってくるわけではない! よって????の部分はエラーにはならず処理が動き、emptyどないので、falseとなる。 isEmptyメソッドはクラスメソッド(プロトタイプメソッド)なので、 つまり、返却されたオブジェクトの[[prototype]]がPowerArray.prototype のまま変わってないということがわかる。 なぜこういう動きとなるのか次問題にて解説
20
実行コンテキストとなるオブジェクトのconstructorプロパティを利用して、インスタンスしているから。 また、実行コンテキストのconstructorプロパティとは、実行コンテキストであるオブジェクトのクラス(コンストラクタ関数)のことである。 前問の例では以下の通りとなる。 arr.constructor === PowerArray;
21
特別な静的な getter Symbol.species を追加することができます。 getter Symbol.species では、利用するコンストラクタ関数を指定します。 これがあると、filterやmap関数は↑のゲッター関数で指定されたコンストラクタ関数を利用して値を返すようになる! なので、Array型を返したい場合は Arrayクラス=コンストラクタ関数なので、これを指定すればよい。 class PowerArray extends Array { isEmpty() { return this.length === 0; } // 組み込みのメソッドは、これをコンストラクタとして使います!! static get [Symbol.species]() { return Array; } } let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false // filter はコンストラクタとして arr.constructor[Symbol.species] を使って新しい配列を作ります let filteredArr = arr.filter(item => item >= 10); // filteredArr は PowerArray ではなく Array です alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function また、Map や Set などの他のコレクションも同様に動作する。 これらも Symbol.species を使用しています。
22
通常のクラス同士の継承は、 コンストラクタ関数自体のプロトタイプの継承と コンストラクタ関数.プロトタイプオブジェクトの継承の二つがあった。 だけど組み込み同士の場合 コンストラクタ関数自体のプロトタイプの継承がない。 なので、静的なフィールドは継承されない。 まあ確かにいらないか
23
それはある。試したら動いたので。 組み込み同士がないだけ。
24
左辺のオブジェクトが右辺のクラス(コンストラクタ関数)に属するかチェックする時に利用する演算子。 継承も考慮される。 構文は次の通りです: obj instanceof Class それは obj が Class (または、それを継承しているクラス)に属している場合に true を返します。 class Rabbit {} let rabbit = new Rabbit(); // Rabbit クラスのオブジェクト? alert( rabbit instanceof Rabbit ); // true
25
当然コンストラクタ関数もいける。 class の代わり function Rabbit() {} alert( new Rabbit() instanceof Rabbit ); // true また Array のような組み込みクラスでも動作します。: let arr = [1, 2, 3]; alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true
26
左辺のオブジェクトのプロトタイプチェーンのうち、 右辺のClass.prototypeとマッチするかチェックしている。 言い換えると、以下のような比較を行います: obj.__proto__ == Class.prototype obj.__proto__.__proto__ == Class.prototype obj.__proto__.__proto__.__proto__ == Class.prototype ... // いずれかが true の場合、 true が返却されます // そうでない場合、チェーンの末尾に到達すると false を返します
27
右辺のクラス側に、 静的メソッド [Symbol.hasInstance](obj)を追加した場合、比較時、このメソッドが呼ばれるようになる。 そのメソッドの引数は左辺のオブジェクトであり、trueを返せば、プロトタイプチェーンのチェックをせずに戻り値をtrueとすることができる。 例えば、以下では、オブジェクトにとあるプロパティが存在していれば、継承関係なく、trueとみなしてしまっている。 // catEat プロパティをもつものは animal と想定する // instanceOf チェックを設定 class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj)が呼ばれます
28
objA.isPrototypeOf(objB)はobjAがobjBのプロトタイプチェーンに含まれるかチェックなので、 いいかえると Class.prototype.isPrototypeOf(obj) になる。 オブジェクトでもいけるけど、 プロトタイプ側が利用するメソッドだとと覚えておこう! Class.prototypeがobjのプロトタイプチェーンであるか? っていみ
29
[object Object]
30
だいたいtoStringは配列とかだったら要素が出るようにArray.prototype でオーバーライドされてカスタマイズされている。 しかしObject.prototype.toString をArrayのインスタンスを実行コンテキストとして呼び出すとカスタマイズされてない処理が行われる。 数値の場合、それは [object Number] になります。 真偽値の場合、[object Boolean] になります。 null の場合: [object Null] undefined の場合: [object Undefined] 配列の場合: [object Array] …など (カスタマイズ可能). デモを見てみましょう: // 使いやすくするために toString メソッドを変数にコピー let objectToString = Object.prototype.toString; // これの型はなに? let arr = []; // call関数を使用している。 alert( objectToString.call(arr) ); // [object Array]
31
特別なオブジェクトプロパティ Symbol.toStringTag を使用してカスタマイズできます。 例: let user = { [Symbol.toStringTag]: 'User' }; //Object.prototype.toStringを短縮してかいてる alert( {}.toString.call(user) ); // [object User] // user.toString()でも変わんなさそう // 環境固有のオブジェクトとクラスのtoStringTag: alert( window[Symbol.toStringTag]); // window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest ほとんどの環境固有のオブジェクトには、このようなプロパティがあります。これはいくつかのブラウザ固有の例です。: alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
32
型やクラスのチェックメソッド
33
typeof 演算子 プリミティブ型が対象 console.log(typeof 42); // Expected output: "number" {}.toString メソッド プリミティブ, 組み込みオブジェクト, Symbol.toStringTag をもつオブジェクト が対象 instanceof 演算子 オブジェクトが対象 ご覧のように、{}.toString は技術的には “より高度な” typeof です。 そして、instanceof 演算子は、クラス階層を扱っていて継承を考慮したクラスのチェックをしたい場合に本当に輝きます。
34
aはたしかにBコンストラクタ関数によってインスタンスされたオブジェクトではない。 しかし、内部的には a instanceof B の処理は aのプロトタイプチェーンがB.prototype に属しているかチェックしている。 a.__proto__ はA.prototypeである。 また、A.prototype==B.prototype となるように同じ空のオブジェクトを代入していることから a.__proto__ == B.prototype が成り立つ。
35
インスタンスメソッドなので、User.prototypeに対し、 Object.assignでメソッドをアサインさせてやれば良い // メソッドをコピー Object.assign(User.prototype, sayHiMixin); // これで User は sayHi できます new User("Dude").sayHi(); // Hi Dude!
36
継承は一つのオブジェクトからのみ行えるが、 複数のオブジェクトから継承したいってときに利用できる。 JavaScriptは多重継承をサポートしていませんが、Object.assignなどでプロトタイプにプロパティをコピーすることでミックスインが実装できます。
37
まず登録する関数はどのように管理するか? _eventHandersオブジェクトを定義し、 プロパティにはeventNameをもつようにする。 各eventNameプロパティの値には関数をリスト形式で管理する。 要するに以下のように登録できる。 _eventHanders[evemtName] = 呼び出すハンドラのリスト(一つのイベントネームに対し、複数ハンドラを登録可能!) onメソッドは以上の登録処理を行い offメソッドはハンドラのリストから対象ハンドラを削除する。 トリガ機能は、 eventNameを引数にとり、管理している関数を呼び出す。 ハンドラ呼び出す際の引数の渡せるようサポートする。 trigger(eventName, ...args) 答えは以下 let eventMixin = { /** * イベントの購読, 使い方: * menu.on('select', function(item) { ... } */ on(eventName, handler) { if (!this._eventHandlers) this._eventHandlers = {}; if (!this._eventHandlers[eventName]) { this._eventHandlers[eventName] = []; } this._eventHandlers[eventName].push(handler); }, /** * 購読のキャンセル 使い方: * menu.off('select', handler) */ off(eventName, handler) { let handlers = this._eventHandlers && this._eventHandlers[eventName]; if (!handlers) return; for(let i = 0; i < handlers.length; i++) { if (handlers[i] == handler) { handlers.splice(i--, 1); } } }, /** * イベントを生成してデータをアタッチ * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { if (!this._eventHandlers || !this._eventHandlers[eventName]) { return; // イベントに対応するハンドラがない場合 } // ハンドラ呼び出し this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } }; つまるところミックスインというのは処理の骨格を実装できる。 それを扱うクラスはそのミックスインした処理をラップしたりして扱うことができる。
38
setTimeoutは非同期なので、キャッチされない。 スケジュールされた関数の内側の例外をキャッチするためには、その関数の中に try..catch が必要です。: setTimeout(function() { try { noSuchVariable; // try..catch がエラーをハンドリングします! } catch (e) { alert( "error is caught here!" ); } }, 1000);
39
name エラー名です。未定義変数の場合、それは "ReferenceError" です。 message エラー詳細に関するテキストメッセージです。 ほとんどの環境では、その他非標準のプロパティが利用可能です。最も広く使われ、サポートされているのは以下です: stack※非標準 現在のコールスタックです: エラーに繋がったネスト呼び出しのシーケンスに関する情報を持つ文字列です。デバッグ目的で使われます。 try { lalala; // エラー, 変数が宣言されていません! } catch(err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...スタック呼び出し) // 全体としてエラーを表示する事もできます // エラーは "name: message" として文字列に変換されます alert(err); // ReferenceError: lalala is not defined }
40
// 全体としてエラーを表示する事もできます // エラーは "name: message" として文字列に変換されます alert(err); // ReferenceError: lalala is not defined
41
エラーの詳細が必要ない場合、catch はそれを省略できます: try { // ... } catch { // <-- (err) なし // ... }
42
潜在的なエラーが見込まれる場合 例えば、ネットワーク経由でサーバまたは別のソースから受信したデータをデコードするために JSON.parse(受信データ)を利用したときなど。 受診データが不正であった場合のエラーをキャッチできる。 let json = "{ bad json }"; try { let user = JSON.parse(json); // <-- エラーが起きたとき... alert( user.name ); // 動作しません } catch (e) { // ...実行はここに飛びます alert( "Our apologies, the data has errors, we'll try to request it one more time." ); alert( e.name ); alert( e.message ); }
43
throw 演算子はエラーを生成します。 構文は次の通りです: throw <error object>
44
技術的には、エラーオブジェクトとしてなんでも使うことができます。たとえ、数値や文字列のようなプリミティブでもOKです。しかし、name と message プロパティを持つオブジェクトを使うのがベターです(組み込みのエラーと互換性をいくらか保つために)。 通常は組み込みの Error クラスを継承しているエラーオブジェクトがよい! →instanceof演算子で判断利用が可能のため
45
Error, SyntaxError, ReferenceError, TypeError などです。我々もエラーオブジェクトを作るのにそれらが使えます。 構文は次の通りです: let error = new Error(message); // or let error = new SyntaxError(message); let error = new ReferenceError(message); // ... 組み込みのエラー(任意のオブジェクトではなく、エラーのみ)では、name プロパティはコンストラクタの名前と全く同じになります。そして message は引数から取られます。 例: let error = new Error("Things happen o_O"); alert(error.name); // Error alert(error.message); // Things happen o_O
46
プロパティがあることを期待しているのに、ない場合はSyntaxErrorでいいかも? リファレンスエラーは明示的に投げることなさそう JSON.parseが生成するエラーはSyntaxError です。 try { JSON.parse("{ bad json o_O }"); } catch(e) { alert(e.name); // SyntaxError alert(e.message); // Unexpected token o in JSON at position 0 } そして、我々のケースでは、ユーザは必ず "name" を持っていると仮定するので、name の欠落もまた構文エラーとして扱います。 なので、それをスローするようにしましょう: let json = '{ "age": 30 }'; // 不完全なデータ try { let user = JSON.parse(json); // <-- エラーなし if (!user.name) { throw new SyntaxError("Incomplete data: no name"); // (*) } alert( user.name ); } catch(e) { alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name }
47
全てのエラーを黙殺させてしまうため、 想定してないエラーも握りつぶされてしまう可能性がある。 キャッチは想定していたエラーだけを処理し、想定してないオブジェクトは“再スロー” するべき let json = '{ "age": 30 }'; // 不完全なデータ try { let user = JSON.parse(json); if (!user.name) { throw new SyntaxError("Incomplete data: no name"); } blabla(); // 予期しないエラー alert( user.name ); } catch(e) { if (e.name == "SyntaxError") { alert( "JSON Error: " + e.message ); } else { throw e; // 再スロー (*) } }
48
finally を持つ場合があります。 もし存在する場合、それはすべてのケースで実行します。: 拡張された構文はこのようになります。: try { ... コードを実行しようとします ... } catch(e) { ... エラーを処理します ... } finally { ... 常に実行します ... }
49
finally 句は try..catch の前に何かを開始して、どのような結果であれファイナライズをしたいときに頻繁に使われます。 例えば計測に関しては、正常ケースでも異常ケースでも、最後に計測完了の処理を行う必要がある。 その際にfinallyがつかえる
50
エラーはキャッチせず、投げられっぱなしになる。→上位側でキャッチ可能、キャッチしない場合はいつもの感じのエラーとなる。 ただし構文の外に出る前にfinallyが実行される。 catch 句がない try..catch 構造も役立ちます。私たちはここでエラーを正しく処理したくないが、開始した処理が完了したことを確認したいときに使います。 function func() { // (計測など)完了させる必要のあるなにかを開始する try { // ... } finally { // すべてが死んでいても完了させる } } 上のコードでは、try の内側のエラーは常に抜けます。なぜなら catch がないからです。しかし finally は実行フローが外部に移る前に機能します。
51
ブラウザでは関数を特別な window.onerror プロパティに代入することができます。それはキャッチしていないエラーの場合に実行されます。 構文: window.onerror = function(message, url, line, col, error) { // ... }; message エラーメッセージ url エラーが起きたスクリプトのURL line, col エラーが起きた行と列番号 error エラーオブジェクト 例: <script> window.onerror = function(message, url, line, col, error) { alert(`${message}\n At ${line}:${col} of ${url}`); }; function readData() { badFunc(); // おっと、何かがおかしいです! } readData(); </script> グローバルハンドラー window.onerror の役割は、通常スクリプトの実行の回復ではありません – プログラミングエラーの場合、恐らくそれは不可能なので開発者にエラーメッセージを送ります。
52
finallyはtryやcatchブロック内からの他の処理への飛び出しがあったとしても、その前に実行される。 finallyは最後に実行する保証がある。 その違いは、関数内のコードを見ると明らかになります。 もし try..catch の “飛び出し” がある場合、振る舞いは異なります。 例えば、try..catch の中で return がある場合です。finally 句は try..catch が どのような終わり方の場合にでも 動作します。たとえ、return 文経由でさえも。 function f() { try { alert('start'); return "result"; } catch (e) { /// ... } finally { alert('cleanup!'); } } f(); // cleanup! …もしくは次のように throw がある場合: function f() { try { alert('start'); throw new Error("an error"); } catch (e) { // ... if("can't handle the error") { throw e; } } finally { alert('cleanup!') } } f(); // cleanup! ここで finally はクリーンアップを保証します。もし f の終わりにコードをおいた場合は実行されない場合があります。
53
エラーは message, name 、望ましくは stack のような基本のエラープロパティをサポートすべきです。 ですが、他にも独自のプロパティを持つかもしれません。例えば HttpError オブジェクトであれば、 404, 403 もしくは 500 といった値をとる statusCode プロパティです。 JavaScript は任意の引数で throw できるので、技術的にはカスタムのエラークラスは Error から継承する必要はありません。 しかし、継承しているとエラーオブジェクトを識別する obj instanceof Error を使えるようになります。そのため、継承しておくほうのがベターです。 アプリケーションを開発するにつれ、独自のエラーが自然に階層を形成します。たとえば、 HttpTimeoutError は HttpError を継承する、といったように。
54
カスタムエラーを作成する。 データのチェック(バリデート)でエラーがおきたので、バリデーションエラーとする。 class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } // Usage function readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new ValidationError("No field: age"); } if (!user.name) { throw new ValidationError("No field: name"); } return user; } // try..catch での動作例 try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { alert("Invalid data: " + err.message); // Invalid data: No field: name } else if (err instanceof SyntaxError) { // (*) alert("JSON Syntax Error: " + err.message); } else { throw err; // 知らないエラーなので、再スロー (**) } }
55
プロパティが存在しなかったり、誤ったフォーマット(age が文字列値のような)など。ここで、存在しないプロパティに対するより具体的なクラス PropertyRequiredError を作りましょう。欠落しているプロパティについての追加の情報を保持します。 class PropertyRequiredError extends ValidationError { constructor(property) { super("No property: " + property); this.name = "PropertyRequiredError"; this.property = property; } }
56
バリデーションエラークラスなど、上位エラーオブジェクトでthis.constructor.name;を利用する。 ↑これはオブジェクトの[[prototype]]のもちもののプロパティである。 class ValidationError extends Error { constructor(message) { super(message); this.name = this.constructor.name; } }
57
例外を扱う際の考え方として、 例外を出す外側で例外のチェックをする場合は、エラーとしての考え方もより抽象的である方がよい。 例えばreadUserは ValidationErrorやSyntaxErrorを出すが、 これは外側の上位レベルでは、あまり関心のない出来事である。 抽象度を上げて考えると、ただ読み込みに失敗したとだけわかればよい。 そのため、上位レベルではエラーも同様に抽象化するべきである。
58
例外のラッピングと言われるテクニック 汎用的な “データ読み込み” エラーを表現する新しいクラス ReadError を作り、 ValidationError や SyntaxError が内部で発生したらReadErrorを作り再スローする。 ReadError オブジェクトは自身の cause プロパティに元のエラーへの参照を保持させておく。 以下全容 class ReadError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = 'ReadError'; } } class ValidationError extends Error { /*...*/ } class PropertyRequiredError extends ValidationError { /* ... */ } function validateUser(user) { if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } } function readUser(json) { let user; try { user = JSON.parse(json); } catch (err) { if (err instanceof SyntaxError) { throw new ReadError("Syntax Error", err); } else { throw err; } } try { validateUser(user); } catch (err) { if (err instanceof ValidationError) { throw new ReadError("Validation Error", err); } else { throw err; } } } try { readUser('{bad json}'); } catch (e) { if (e instanceof ReadError) { alert(e); // Original error: SyntaxError: Unexpected token b in JSON at position 1 alert("Original error: " + e.cause); } else { throw e; } } 外部のコードは instanceof ReadError をチェックするだけですんだ!
59
例えばスクリプトとモジュールの読み込みです 指定された src でスクリプトをロードする関数 loadScript(src) を見てください。: function loadScript(src) { // <script> タグを作りページに追加します // 指定された src のスクリプトの読み込みを開始し、完了時に実行します let script = document.createElement('script'); script.src = src; document.head.append(script); } これは、指定された src をもつ、新たに動的に作成された <script src="…"> をドキュメントに挿入します。ブラウザは自動的に読み込みを開始し、完了時に実行します。 使用例: // スクリプトを読み込み、実行する loadScript('/my/script.js'); スクリプトは “非同期に” 実行され、すぐに読み込みを開始しますが、後で(関数がすでに終了した後)実行されます。
60
コールバック処理を実装する! loadScript に2つ目の引数に、スクリプトが読み込まれたときに実行する callback 関数を追加しましょう: ちょっと難しいがスクリプトの読み込みが完了したらonloadイベントがはしるため、そこに関数を登録しておくことで、コールバック処理を実装できる。 function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(script); document.head.append(script); } loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { alert(`Cool, the ${script.src} is loaded`); alert( _ ); // ロードされたスクリプトで宣言されている関数 });
61
読みこみスクリプトでエラーがあればオンエラーイベントが起動するので、関数を登録する。 正常ケースでも異常ケースでも受け取ったコールバック関数を起動させることで、 コールバック関数内部で 正常ケースと異常ケースを分けて定義させるのがよい。 またJavaScript においてよく使われるテクニックである function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } これは成功時に callback(null, script) を呼び、それ以外の場合には callback(error) を呼びます。 使用方法: loadScript('/my/script.js', function(error, script) { if (error) { // エラー処理 } else { // スクリプトの読み込みが成功 } });
62
これは “コールバック地獄” や “破滅のピラミッド” と呼ばれます。 Promiseを使えば解決できる。
63
Promiseオブジェクト
64
let promise = new Promise(function(resolve, reject) { // executor });
65
コンストラクタの引数にはexecutor関数を定義する。 new promise が作成されると、executor は自動的に実行されます。 executorの実行部では、 非同期メソッドを記述し、 JavaScript側から提供される関数 (executor関数実行時、Promseのコンストラクタから渡されてそう。) resolve, rejectを呼び出すことによって、 promiseオブジェクトのstateの管理を行うことができる。 例えばsetTimeoutでresolve 前問であったようなスクリプト読み込みにおける、onload イベントにてresolve
66
resolve関数は resolve(value)で呼び出す。 ジョブが正常に終了した場合。結果は valueになる。 reject関数は reject(error)で呼び出す。 エラーが発生した場合、error はエラーオブジェクトを指定する。 →これらは、promiseのresultに登録され、コールバック関数でうけとれる。 つまり正常終了とエラーの判断は、 executor関数内で自分で定義する
67
state – 最初は "pending(保留中)" であり、その後 resolve が呼ばれると "fulfilled(完了)" 、もしくは reject が呼ばれると "rejected(拒否)" に変更されます。 result – 初期値は undefined です。その後、resolve(value) が呼ばれると value に、reject(error) が呼ばれると error になります
68
成功ならstate:”fulfilled” result:value(resolve呼ぶ時のパラメータ) 失敗ならstate:”rejected” result:error(reject呼ぶ時のエラーオブジェクト) 例 let promise = new Promise(function(resolve, reject) { // promise が作られたとき、関数は自動的に実行されます // 1秒後、ジョブが "done!" という結果と一緒に完了したことを合図します setTimeout(() => resolve("done!"), 1000); });
69
.then, .catch, .finally を使って、promiseの結果をうけてから、処理をはじめることがきます。サブスクライブするともいう。 これらは即pendingのプロミスを返すため、以降のコードはそのまま動いていく。 サブスクライブした処理は、 promiseの状態次第でコールバック処理が行われる。 正確には、 promise が pending の場合、.then/catch/finally ハンドラは結果を待ちます。 promiseのstateが変わったら、現在の処理が完了後に処理されるようになる。
70
thenはコールバックを登録するだけで、 待つわけではないので注意!! then() メソッドは常に新しい Promise インスタンスを返す なのでプロミスチェーンのthenは最後までしっかり実行される。 コールバックの登録のみ行われ、それぞれプロミスの状態が変わり次第の実行となる! thenには成功用とエラー用の二つの関数をパラメータとしてわたし、登録できる promise.then( function(result) { /* 成功した結果を扱う */ }, function(error) { /* エラーを扱う */ } ); .then の最初の引数の関数は、promise が解決(resolve)されたときに実行され、resolve(value)のvalueを受け取ります。 .then の2つ目の引数の関数は、promise が拒否(reject)されたときに実行され、reject(エラーオブジェクト) のエラーオブジェクトを受け取ります。 例えば、これは正常に解決された promise の動きです: let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve("done!"), 1000); }); doneの値が下のresultに設定される! // resolve は .then の最初の関数を実行する promise.then( result => alert(result), // 1秒後に "done!" を表示 error => alert(error) // 実行されない ); もし、正常完了の場合だけを扱いたい場合は、.then には引数を1つの関数だけを指定します: let promise = new Promise(resolve => { setTimeout(() => resolve("done!"), 1000); }); promise.then(alert); // 1秒後に "done!" を表示
71
エラーにのみ関心がある場合は、 .catch(function) が使えます。 .then(null, function) と全く同じ。 let promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Whoops!")), 1000); }); // .catch(f) は promise.then(null, f) と同じです promise.catch(alert); // 1秒後に "Error: Whoops!" を表示 .catch(f) の呼び出しは、.then(null, f) の完全な類似物であり、単に簡略化したものです。
72
正常でもエラーでも呼び出されるし、 引数なしのハンドラを登録しとけばいいので、なんかの処理のファイナライザをかくのに最適 あとプロミスオブジェクトはおなじのをかえす .finally(f) 呼び出しは、Promise が完了したとき(解決または拒否になったとき)に必ず実行されるという意味で .then(f, f) に似ています。 finally はクリーンアップを実行するのに便利なハンドラです。例えば、結果がどちらであっても、もう不要となったので読み込み中のインジケータを停止するなどです。 finallyに登録するハンドラは引数なし。 このようになります: new Promise((resolve, reject) => { /* 時間のかかる処理を行い、その後 resolve/reject を呼び出す */ }) // 成功か失敗かは関係なく、promise が確定したときに実行されます .finally(() => 読込中のインジケータを停止する ) // したがって、読み込み中のインジケータは結果/エラーを処理する前に必ず停止されます .then(result => 結果を表示する, err => エラーを表示する) 重要! finallyはpromiseオブジェクトをそのまま引き継いで返すので、上記の例の通り.thenや.catchを実行できることを認識せよ! また上記例のfinallyやthenは、待つわけではなく購読するだけで、現在のコード実行はおこなっていく。 なのでfinallyの処理が完了したらthenが動くように購読されている。 finally は promise の結果を処理する手段ではないので、これは非常に便利です。
73
Settledという
74
関数はpromiseオブジェクトを返すことで、 非同期処理を管理でき、.thenメソッドが使用可能となる。 スクリプトの読み込みやエラーがあったところで、コールバック関数をよんでいたが、resolve, rejectを呼んで、ステータスを変更するだけでよい。 function loadScript(src) { return new Promise(function(resolve, reject) { let script = document.createElement('script'); script.src = src; script.onload = () => resolve(script); script.onerror = () => reject(new Error("Script load error: " + src)); document.head.append(script); }); }
75
loadScriptをよぶことで、promiseオブジェクトを作成し、 thenメソッドにて、成功時、エラー時の処理を購読! 以下例のように複数のハンドラを定義可能 let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"); promise.then( script => alert(`${script.src} is loaded!`), error => alert(`Error: ${error.message}`) ); promise.then(script => alert('Another handler...'));
76
Promiseは使いやすい。 Promise では .then を何度でも呼び出すことができ、ハンドラを複数登録できる。
77
以下無名関数とほぼおなじ。 promise.then( result => alert(result), ); alert関数は引数をとり、その引数にはvalueが入ってくるので、alert関数だけでもそりゃ動く。
78
promise.thenによるチェーン構造のことである。 thenメソッドは、promiseオブジェクトを返すので、チェーン化できる。 最初はpending の状態のpromiseである。 購読した関数の処理が全部終わると、 thenメソッドで作成した promiseが更新される。(status:resolvedとして)、返却した値はresultに設定される。 thenにて作成されるプロミスのかわりに、プロミスを返却することもできる。 なので、次の .then はその結果と一緒に呼ばれます。 new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); // (*) }).then(function(result) { // (**) alert(result); // 1 return result * 2; }).then(function(result) { // (***) alert(result); // 2 return result * 2; }).then(function(result) { alert(result); // 4 return result * 2; }); ただ、thenチェーンで非同期メソッドを利用してないので、あんまこれは使われないかも
79
技術的には単一の Promise に複数の .then を追加することもできます。これはチェーンではありません 例: let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); ここで行ったことは、1つの promise に対して複数のハンドラの設定です。これらは結果を相互に渡しません。代わりにそれぞれが独立して処理をします。
80
ハンドルにて返す値は プリミティブだけでなく promiseかthenableが指定できる。 promiseかthenableを返すと promiseなら非同期処理の状態と結果が thenで作成されてたpromise に登録される。 thenableならthenメソッドに定義したexecutorが実行され、状態と結果が thenで作成されてたpromise に登録される。 ※thenは同期的にpendingのpromiseオブジェクトを返すので、そのpromiseを更新する感じ。 new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }).then(function(result) { alert(result); // 1 return new Promise((resolve, reject) => { // (*) setTimeout(() => resolve(result * 2), 1000); }); }).then(function(result) { // (**) alert(result); // 2 return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); }); }).then(function(result) { alert(result); // 4 }); 1秒たったら処理して1秒たったら処理してみたいな感じ
81
“thenable” オブジェクトという、メソッド .then をサポートしたオブジェクトを返すことがある。 thenableのthenメソッドでは、 非同期処理を記述でき、resolve,reject引数をとり、コールバックとして任意のタイミングで呼び出しできるので、 promiseオブジェクトで定義していた、executorなどの処理と同等の内容を記載でき、代わりとして利用できる。 thenでthenableを返すと内部のほうでthenの処理が実行され、結果が作成されていたpromiseに登録される。 thenを実装しているからといって、そのthenメソッドをこちらで直接呼ぶわけではなく、組み込み処理によって呼ばれて管理されている。 既存のクラスに対して、thenメソッドを付与するだけで使えるのが利点。 また、プロパティや外部変数も利用できるのがメリット 以下のとおり new Promise(resolve => resolve(1)) .then(result => { return new Thenable(result); // (*) }) .then(alert); // 1000ms 後に 2 を表示 この思想は、サードパーティライブラリが彼ら自身の “promise 互換な” オブジェクトを実装できるというものです。それらは拡張されたメソッドのセットを持つことができ、promiseとも互換がある。 Thenableの作り方は以下のとおり class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // function() { native code } // 1秒後に this.num*2 で resolve する setTimeout(() => resolve(this.num * 2), 1000); // (**) } }
82
aaaaaaが優先されて表示され、 次にasが表示される。 やっぱexecuterは同期的に動く
83
thenに一つは非同期アクションを含めて、 promiseかthenableで返すこと。 thenの中で同期的な処理しかしないなら別のthenや他のコードに混ぜれると思う
84
フェッチからjson化の流れが二回あるのでloadJson として共通化できる。 thenメソッドの処理を戻り値としてるので、プロミスがかえる function loadJson(url) { return fetch(url) .then(response => response.json()); } loadJsonをラップするGitHub用のユーザ取得メソッドを定義(ここはやらなくてもいいが、やったほうが綺麗な気がする) function loadGithubUser(name) { return loadJson(`https://api.github.com/users/${name}`); } ↑で十分再利用関数として定義できた。 ついでに その後のアバター画像表示処理はそのまま関数宣言におきかえる function showAvatar(githubUser) { return new Promise(function(resolve, reject) { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); }, 3000); }); } 利用する時はこんな感じになる 3000); }); } // Use them: loadJson('/article/promise-chaining/user.json') .then(user => loadGithubUser(user.name)) .then(showAvatar) .then(githubUser => alert(`Finished showing ${githubUser.name}`)); // ... ↑見やすくなった!!
85
promise が reject されると、制御は最も近い reject ハンドラに移ります。この動きは実際に非常に便利です。 ご覧の通り、.catch は直後である必要はありません。1つまたは複数の .then の後に現れるかもしれません。 なので、上の例でいうと、 fetch('https://no-such-server.blabla') // この処理でrejects される .then(response => response.json())←のハンドラ実行はスキップされる。 また、サイトはすべて問題ありませんが、レスポンスが有効な JSON でない可能性もあります。すべてのエラーをキャッチする最も簡単な方法はチェーンの末尾に .catch を追加することです。 fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(response => response.json()) .then(githubUser => new Promise((resolve, reject) => { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); }, 3000); })) .catch(error => alert(error.message)); 通常、この .catch は呼ばれません。ですが、上の promise のいずれかがreject した場合(ネットワーク問題 or 無効な json など)、それをキャッチします。
86
executor と promise ハンドラのコードは 見えないtry catchをもっており、投げられたエラーは勝手にrejectしたものとして扱ってくれる! ↑既存コードを変えなくていいので便利!! ただし注意! 暗黙的やtry catch をもっているということで、try の中でのエラーしか扱ってくれない ! どういうことかというと、 非同期の処理はtry内の構文でエラーが発生しても、エラー発生時はtry構文外となってしまう。 →setTimeoutでエラーを発生させたとしても、エラーはキャッチされない。 なので、同期的なエラーしか自動的にrejectされたものとして扱ってくれないので注意 これは次のと同じように動作します: new Promise(function(resolve, reject) { reject(new Error("Whoops!")); }).catch(alert); // Error: Whoops! executor にある "見えない try..catch" はエラーを自動的にキャッチし reject された promise として扱っています。 これは executor だけでなくハンドラの中でも同様です。.then ハンドラの中で throw した場合、promise の reject を意味するので、コントロールは最も近いエラーハンドラにジャンプします。 ここにその例があります: new Promise(function(resolve, reject) { resolve("ok"); }).then(function(result) { throw new Error("Whoops!"); // promise を rejects }).catch(alert); // Error: Whoops! また、これは throw だけでなく同様にプログラムエラーを含む任意のエラーに対してです: new Promise(function(resolve, reject) { resolve("ok"); }).then(function(result) { blabla(); // このような関数はありません }).catch(alert); // ReferenceError: blabla is not defined 最後の .catch は明示的な reject だけでなく、上記のハンドラのような偶発的なエラーもキャッチします。
87
キャッチされてから、promiseが戻り、 そのままthenの処理が行われる。 .catch の中で throw する場合、制御は次の最も近いエラーハンドラに移ります。 そして、エラーを処理して正常に終了すると、次に最も近い成功した .then ハンドラに続きます。
88
thenのここうごく??のところは動かず、スルーされる、 エラーの場合は一番近いcatchがうごく!
89
いったん同期的な処理が全部実行される。 そのあとマイクロタスクキューをさがしてキャッチされてないなーってわかれば、 スタックされたままのrejectとなり、通常のエラーとして扱われ、コンソールでエラーが出力される。 しかし、非同期処理がエラーになるだけなので処理落ちはしないはず エラーの場合、promise は “rejected” になり、実行は最も近い reject ハンドラにジャンプします。ですが、上の例にそのようなハンドラはありません。そのため、エラーは “スタック” します(行き詰まります)。 実際、コード内の通常の未処理のエラーと同様、このような場合は何かが誤っていることを意味します。 通常のエラーが発生し、try..catch でキャッチされない場合何が起こるでしょうか?スクリプトはコンソールにメッセージを表示し終了します。同様のことが、未処理の promise の reject でも発生します。 JavaScript エンジンはこのような reject を追跡し、その場合にはグローバルエラーを生成します。上の例を実行すると、コンソールでエラーを見ることができます。
90
ブラウザでは、イベント unhandledrejection を使ってキャッチできます。: イベントエラーハンドリングされていない Promise が拒否されたときにグローバルスコープに送られるイベントです! window.addEventListener('unhandledrejection', function(event) { // イベントオブジェクトは2つの特別なプロパティを持っています: alert(event.promise); // [object Promise] - エラーを生成した promise alert(event.reason); // Error: Whoops! - 未処理のエラーオブジェクト }); new Promise(function() { throw new Error("Whoops!"); }); // エラーを処理する catch がない このイベントは HTML 標準 の一部です。 エラーが発生し .catch がない場合、unhandledrejection ハンドラが発火し、エラーに関する情報を持っている event オブジェクトが渡ります。なので、その情報を使い、何かをすることができます。 通常、このようなエラーはリカバリ不可なので、最善の方法はユーザにその問題を知らせ、サーバへそのインシデントについて報告することです。
91
解答: いいえ、実行されません: new Promise(function(resolve, reject) { setTimeout(() => { throw new Error("Whoops!"); }, 1000); }).catch(alert); チャプターの中で言った通り、関数コードの周りには "暗黙の try..catch" があります。そのため、すべての同期エラーは処理されます。 しかし、ここではエラーは executor が実行中でなく、その後に生成されます。したがって、promise はそれを処理できません。 通常のエラーとして扱われてしまう。 期待した動作をさせたいなら、setTimeoutで渡す関数の中にtry catch構文をかき、 キャッチしたらrejectするようにせよ。
92
promise.allは同期的に全ての処理完了待つわけではなく、 引数のイテラブルからpromiseを一つにまとめ、新しいpromiseとして管理する。 全てのpromiseが完了したタイミングで、promiseのステータスやリザルト値が更新される。 このpromiseのresultは、結果の配列が設定される。 なので、このpromise にthenなどでコールバックを登録することで、 全ての非同期処理を並列に実行し、全てが完了したタイミングでコールバックを起動することができる! 並列に複数の promise を実行し、すべてが準備できるまで待ちたいとしましょう。 例えば、平行で複数の URL をダウンロードし、すべてが完了したらコンテンツを処理する場合です。 これが Promise.all の目的です。 構文は次の通りです: let promise = Promise.all(iterable); Promise.all は iterable(反復可能, 通常は promise の配列)を取り、新しい promise を返します。 新しい promise は、配列の各 promise がすべて解決され、それらの結果を配列に持つと解決(resolve)されます。
93
並列で動き、三秒後に全て終わる。 promiseがかえってくるので、.thenでresult値をアラートする。 result値は配列となっており、順番も定義順となるので [1,2,3]となる。
94
fetch関数は非同期実行を行う関数でpromiseを返す関数である。 配列をmap関数で、それぞれfetch関数を実行し、promiseの配列を取得する。 その後、Promise.allで確認する。 一般的なやり方は、処理データの配列を promise の配列にマップし、それを Promise.all にラップすることです。 例えば、URL の配列がある場合、次のようにしてすべてをフェッチできます: let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://api.github.com/users/jeresig' ]; // 各 url を promise の fetch(github url) へマップする let requests = urls.map(url => fetch(url)); // Promise.all はすべてのジョブが解決されるまで待ちます Promise.all(requests) .then(responses => responses.forEach( response => alert(`${response.url}: ${response.status}`) ));
95
いずれかの promise が reject された場合、Promise.all により返却された promise は即座にエラーと一緒に reject します。 つまり、Promise.allによって返却されるpromiseはrejectされた状態となる。 また、その他のresolveしたpromiseは無視される。つまり、rejectされた時点で完了をまたなくなる。 そのため、catchすることができる。 && みたいなかんじ 例: Promise.all([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).catch(alert); // Error: Whoops! ここでは、2つ目の promise が2秒後に reject されます。それは即座に Promise.all の reject に繋がるため、catch が実行されます。: reject されたエラーは Promise.all 全体の結果になります。 エラー時の場合, 他の promise は無視されます ある promise が reject がされると、Promise.all は即座に reject し、配列の他の promise を完全に忘れます。これらの結果は無視されます。
96
そのまま戻り値のPromiseオブジェクトのresolveに結果が格納される。 通常、Promise.all(iterable) は promise の iterable (ほとんどの場合は配列)を受け付けます。しかし、もしそれらのオブジェクトが promise ではない場合、Promise.resolve でラップします。 →executorやハンドラでのプリミティブでの返却はラップされる! 例えば、ここでは結果は [1, 2, 3] になります: Promise.all([ new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000) }), 2, // Promise.resolve(2) と扱われる 3 // Promise.resolve(3) と扱われる ]).then(alert); // 1, 2, 3 したがって、必要に応じて準備済みの値を Promise.all に渡せます。
97
Promise.allSettled は結果に関わらずすべての promise が解決するまで待ちます。 戻り値はpromiseで、resultの中身はオブジェクトの配列となる。 →エラーは容認されてしまう! 容認されてもいいなら! そのオブジェクト内容は以下の通り。 成功したレスポンスの場合: {status:"fulfilled", value:result} エラーの場合: {status:"rejected", reason:error} 例えば、複数のユーザに対する情報をフェッチしたいとします。たとえ1リクエストが失敗しても、他の結果はほしいです。 Promise.allSettled を使いましょう: let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://no-such-url' ]; Promise.allSettled(urls.map(url => fetch(url))) .then(results => { // (*) results.forEach((result, num) => { if (result.status == "fulfilled") { alert(`${urls[num]}: ${result.value.status}`); } if (result.status == "rejected") { alert(`${urls[num]}: ${result.reason}`); } }); }); 上の行 (*) の results は以下になります: [ {status: 'fulfilled', value: ...レスポンス...}, {status: 'fulfilled', value: ...レスポンス...}, {status: 'rejected', reason: ...エラーオブジェクト...} ] そのため、各 promise に対してステータスと 値 or エラー を取得します。
98
ブラウザはサポートしてない。
99
引数がプロミスのイテラブルを期待する関数を作る。 受け取ったプロミスをそれぞれ.thenでコールバックを登録する。 そのコールバック処理では、成功用、失敗用のオブジェクトを生成する ↑配列内のプロミスに対するコールバックの登録はMap関数を利用して行う。 .thenの戻り値はpromiseなので、それを配列として取得することになる。 生成されるオブジェクトはpromiseの結果に登録される。 上記処理をPromose.allに渡して返す。 if (!Promise.allSettled) { const rejectHandler = reason => ({ status: 'rejected', reason }); const resolveHandler = value => ({ status: 'fulfilled', value }); Promise.allSettled = function (promises) { const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler)); return Promise.all(convertedPromises); }; } Promise.resolve(p)の部分はpromiseじゃなくてプリミティブが配列の中に入ってきた時の対応 値がpromiseの場合は、そのまま返すので、この使い方で問題ない
100
これは Promise.all と同様ですが、すべてが完了するのを待つのではなく、最初の結果(またはエラー)のみを待ちます。 構文です: let promise = Promise.race(iterable); 例えば、ここでは結果は 1 になります: Promise.race([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1 なので、最初の結果/エラーが Promise.race 全体の結果になります。最初の確定した promise が “レースに勝った” 後、それ以外の結果/エラーは無視されます。
その他
その他
谷峻輔 · 12問 · 2年前その他
その他
12問 • 2年前LWC
LWC
谷峻輔 · 100問 · 2年前LWC
LWC
100問 • 2年前LWC2
LWC2
谷峻輔 · 28問 · 2年前LWC2
LWC2
28問 • 2年前Javascript
Javascript
谷峻輔 · 100問 · 2年前Javascript
Javascript
100問 • 2年前javascript2
javascript2
谷峻輔 · 58問 · 2年前javascript2
javascript2
58問 • 2年前js1 JavaScript の基礎
js1 JavaScript の基礎
谷峻輔 · 92問 · 2年前js1 JavaScript の基礎
js1 JavaScript の基礎
92問 • 2年前js2 コード品質、オブジェクト基本、データ型
js2 コード品質、オブジェクト基本、データ型
谷峻輔 · 100問 · 2年前js2 コード品質、オブジェクト基本、データ型
js2 コード品質、オブジェクト基本、データ型
100問 • 2年前js3 データ型
js3 データ型
谷峻輔 · 100問 · 2年前js3 データ型
js3 データ型
100問 • 2年前js4 データ型、関数の高度な機能
js4 データ型、関数の高度な機能
谷峻輔 · 100問 · 2年前js4 データ型、関数の高度な機能
js4 データ型、関数の高度な機能
100問 • 2年前js5 関数の高度な機能 オブジェクトプロパティの設定 プロトタイプ, 継承プロトタイプ, 継承、クラス
js5 関数の高度な機能 オブジェクトプロパティの設定 プロトタイプ, 継承プロトタイプ, 継承、クラス
谷峻輔 · 96問 · 2年前js5 関数の高度な機能 オブジェクトプロパティの設定 プロトタイプ, 継承プロトタイプ, 継承、クラス
js5 関数の高度な機能 オブジェクトプロパティの設定 プロトタイプ, 継承プロトタイプ, 継承、クラス
96問 • 2年前js7 Promise, async/await、ジェネレータ, 高度なイテレーション、モジュール
js7 Promise, async/await、ジェネレータ, 高度なイテレーション、モジュール
谷峻輔 · 96問 · 2年前js7 Promise, async/await、ジェネレータ, 高度なイテレーション、モジュール
js7 Promise, async/await、ジェネレータ, 高度なイテレーション、モジュール
96問 • 2年前js8 その他
js8 その他
谷峻輔 · 57問 · 2年前js8 その他
js8 その他
57問 • 2年前Javaとは〜
Javaとは〜
谷峻輔 · 83問 · 2年前Javaとは〜
Javaとは〜
83問 • 2年前Reactクイックスタート
Reactクイックスタート
谷峻輔 · 15問 · 2年前Reactクイックスタート
Reactクイックスタート
15問 • 2年前デザインパターン
デザインパターン
谷峻輔 · 5問 · 2年前デザインパターン
デザインパターン
5問 • 2年前承認申請
承認申請
谷峻輔 · 16問 · 2年前承認申請
承認申請
16問 • 2年前アプリケーションビルダー2
アプリケーションビルダー2
谷峻輔 · 100問 · 2年前アプリケーションビルダー2
アプリケーションビルダー2
100問 • 2年前アプリケーションビルダー3
アプリケーションビルダー3
谷峻輔 · 100問 · 2年前アプリケーションビルダー3
アプリケーションビルダー3
100問 • 2年前アプリケーションビルダー4
アプリケーションビルダー4
谷峻輔 · 100問 · 2年前アプリケーションビルダー4
アプリケーションビルダー4
100問 • 2年前アプリケーションビルダー5
アプリケーションビルダー5
谷峻輔 · 94問 · 2年前アプリケーションビルダー5
アプリケーションビルダー5
94問 • 2年前アプリケーションビルダー6
アプリケーションビルダー6
谷峻輔 · 100問 · 2年前アプリケーションビルダー6
アプリケーションビルダー6
100問 • 2年前アプリケーションビルダー7
アプリケーションビルダー7
谷峻輔 · 100問 · 2年前アプリケーションビルダー7
アプリケーションビルダー7
100問 • 2年前アプリケーションビルダー8
アプリケーションビルダー8
谷峻輔 · 98問 · 2年前アプリケーションビルダー8
アプリケーションビルダー8
98問 • 2年前アプリケーションビルダー9
アプリケーションビルダー9
谷峻輔 · 70問 · 2年前アプリケーションビルダー9
アプリケーションビルダー9
70問 • 2年前フロー
フロー
谷峻輔 · 42問 · 2年前フロー
フロー
42問 • 2年前レポート、ダッシュボード
レポート、ダッシュボード
谷峻輔 · 60問 · 2年前レポート、ダッシュボード
レポート、ダッシュボード
60問 • 2年前SalesCloud
SalesCloud
谷峻輔 · 18問 · 2年前SalesCloud
SalesCloud
18問 • 2年前標準機能
標準機能
谷峻輔 · 72問 · 2年前標準機能
標準機能
72問 • 2年前宣言的開発
宣言的開発
谷峻輔 · 8問 · 2年前宣言的開発
宣言的開発
8問 • 2年前数式、人力規則
数式、人力規則
谷峻輔 · 18問 · 2年前数式、人力規則
数式、人力規則
18問 • 2年前アクセスレベル
アクセスレベル
谷峻輔 · 63問 · 2年前アクセスレベル
アクセスレベル
63問 • 2年前データ型変換
データ型変換
谷峻輔 · 22問 · 2年前データ型変換
データ型変換
22問 • 2年前オブジェクト、項目
オブジェクト、項目
谷峻輔 · 91問 · 2年前オブジェクト、項目
オブジェクト、項目
91問 • 2年前アプリ、タブ
アプリ、タブ
谷峻輔 · 92問 · 2年前アプリ、タブ
アプリ、タブ
92問 • 2年前自動化
自動化
谷峻輔 · 17問 · 2年前自動化
自動化
17問 • 2年前組織、開発モデル
組織、開発モデル
谷峻輔 · 65問 · 2年前組織、開発モデル
組織、開発モデル
65問 • 2年前ボタン、アクション
ボタン、アクション
谷峻輔 · 69問 · 2年前ボタン、アクション
ボタン、アクション
69問 • 2年前インテグレーション
インテグレーション
谷峻輔 · 40問 · 2年前インテグレーション
インテグレーション
40問 • 2年前ページレイアウト、リストビュー、レコードタイプ、プロセス
ページレイアウト、リストビュー、レコードタイプ、プロセス
谷峻輔 · 46問 · 2年前ページレイアウト、リストビュー、レコードタイプ、プロセス
ページレイアウト、リストビュー、レコードタイプ、プロセス
46問 • 2年前標準オブジェクト
標準オブジェクト
谷峻輔 · 42問 · 2年前標準オブジェクト
標準オブジェクト
42問 • 2年前ServiceCloud
ServiceCloud
谷峻輔 · 16問 · 2年前ServiceCloud
ServiceCloud
16問 • 2年前ユーザ、ライセンス、プロファイル、権限セット
ユーザ、ライセンス、プロファイル、権限セット
谷峻輔 · 41問 · 2年前ユーザ、ライセンス、プロファイル、権限セット
ユーザ、ライセンス、プロファイル、権限セット
41問 • 2年前データインポート、エクスポート
データインポート、エクスポート
谷峻輔 · 3回閲覧 · 12問 · 2年前データインポート、エクスポート
データインポート、エクスポート
3回閲覧 • 12問 • 2年前Chatter
Chatter
谷峻輔 · 21問 · 2年前Chatter
Chatter
21問 • 2年前承認申請
承認申請
谷峻輔 · 15問 · 2年前承認申請
承認申請
15問 • 2年前フロー
フロー
谷峻輔 · 44問 · 2年前フロー
フロー
44問 • 2年前レポート、ダッシュボード
レポート、ダッシュボード
谷峻輔 · 65問 · 2年前レポート、ダッシュボード
レポート、ダッシュボード
65問 • 2年前SalesCloud
SalesCloud
谷峻輔 · 18問 · 2年前SalesCloud
SalesCloud
18問 • 2年前標準機能
標準機能
谷峻輔 · 72問 · 2年前標準機能
標準機能
72問 • 2年前宣言的開発
宣言的開発
谷峻輔 · 8問 · 2年前宣言的開発
宣言的開発
8問 • 2年前アクセスレベル
アクセスレベル
谷峻輔 · 52問 · 2年前アクセスレベル
アクセスレベル
52問 • 2年前数式、人力規則
数式、人力規則
谷峻輔 · 17問 · 2年前数式、人力規則
数式、人力規則
17問 • 2年前データ型変換
データ型変換
谷峻輔 · 22問 · 2年前データ型変換
データ型変換
22問 • 2年前オブジェクト、項目
オブジェクト、項目
谷峻輔 · 89問 · 2年前オブジェクト、項目
オブジェクト、項目
89問 • 2年前アプリ、タブ
アプリ、タブ
谷峻輔 · 100問 · 2年前アプリ、タブ
アプリ、タブ
100問 • 2年前自動化
自動化
谷峻輔 · 18問 · 2年前自動化
自動化
18問 • 2年前組織、開発モデル
組織、開発モデル
谷峻輔 · 60問 · 2年前組織、開発モデル
組織、開発モデル
60問 • 2年前ボタン、アクション
ボタン、アクション
谷峻輔 · 69問 · 2年前ボタン、アクション
ボタン、アクション
69問 • 2年前インテグレーション
インテグレーション
谷峻輔 · 40問 · 2年前インテグレーション
インテグレーション
40問 • 2年前ページレイアウト、リストビュー、レコードタイプ、プロセス
ページレイアウト、リストビュー、レコードタイプ、プロセス
谷峻輔 · 44問 · 2年前ページレイアウト、リストビュー、レコードタイプ、プロセス
ページレイアウト、リストビュー、レコードタイプ、プロセス
44問 • 2年前標準オブジェクト
標準オブジェクト
谷峻輔 · 32問 · 2年前標準オブジェクト
標準オブジェクト
32問 • 2年前ServiceCloud
ServiceCloud
谷峻輔 · 17問 · 2年前ServiceCloud
ServiceCloud
17問 • 2年前ユーザ、ライセンス、プロファイル、権限セット
ユーザ、ライセンス、プロファイル、権限セット
谷峻輔 · 18問 · 2年前ユーザ、ライセンス、プロファイル、権限セット
ユーザ、ライセンス、プロファイル、権限セット
18問 • 2年前データインポート、エクスポート
データインポート、エクスポート
谷峻輔 · 11問 · 2年前データインポート、エクスポート
データインポート、エクスポート
11問 • 2年前Chatter
Chatter
谷峻輔 · 21問 · 2年前Chatter
Chatter
21問 • 2年前一時
一時
谷峻輔 · 89問 · 2年前一時
一時
89問 • 2年前一時2
一時2
谷峻輔 · 29問 · 2年前一時2
一時2
29問 • 2年前js9 ドキュメント
js9 ドキュメント
谷峻輔 · 80問 · 2年前js9 ドキュメント
js9 ドキュメント
80問 • 2年前問題一覧
1
クラス全体にメソッドを割り当てることもできます。このようなメソッドは static(静的) と呼ばれます。 クラス宣言の中では、次のように static キーワードを付けます。 class User { static staticMethod() { alert(this === User); } } User.staticMethod(); // true
2
通常 static メソッドは、クラスには属するが、特定のオブジェクトには属さない関数を実装するのに使用されます。 1. 比較用のメソッド 例えば、Article オブジェクトがあり、それらを比較するための関数が必要とします。 自然な解決策は、静的メソッド Article.compare を追加することです。: class Article { constructor(title, date) { this.title = title; this.date = date; } static compare(articleA, articleB) { return articleA.date - articleB.date; } } // usage let articles = [ new Article("Mind", new Date(2019, 1, 1)), new Article("Body", new Date(2019, 0, 1)), new Article("JavaScript", new Date(2019, 11, 1)) ]; articles.sort(Article.compare); alert( articles[0].title ); // Body ここでは、Article.compare は記事を比較する手段として、記事の “上位” にいます。記事のメソッドと言うよりはクラス全体のメソッドです。 2 ファクトリーメソッド 別の例は、いわゆる “ファクトリー” メソッドです。 記事の作成には複数の方法が必要です: 与えられたパラメータ(title, date など)による作成 今日の日付の空の記事の作成 … 最初の方法はコンストラクタで実装することができます。そして、2 つ目の方法としてはクラスの静的メソッドを作ることができます。 ここでの Article.createTodays() を見てください: class Article { constructor(title, date) { this.title = title; this.date = date; } static createTodays() { // 思い出してください, this = Article return new this("Today's digest", new Date()); } } let article = Article.createTodays(); alert( article.title ); // Todays digest これで今日のダイジェストを作成する必要があるたびに、Article.createTodays() を呼べます。 3 データベース関連クラスの操作メソッド 静的メソッドは、次のように、データベース関連のクラスでデータベースの検索/保存/削除のためにも使用されます。: // Article は記事を管理するための特別なクラスと仮定します // 記事を削除するための static メソッド: Article.remove({ id: 12345 });
3
静的プロパティも可能で、通常のクラスプロパティと同じように見えますが、先頭に static が付きます。 class Article { static publisher = "Ilya Kantor"; } alert(Article.publisher); // Ilya Kantor
4
プロトタイプチェーンが形成されるので、継承先のクラスでも静的プロパティやメソッドを継承し、使えるようになる。 class Animal { static planet = "Earth"; constructor(name, speed) { this.speed = speed; this.name = name; } run(speed = 0) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } static compare(animalA, animalB) { return animalA.speed - animalB.speed; } } // Inherit from Animal class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } } let rabbits = [ new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5) ]; rabbits.sort(Rabbit.compare); rabbits[0].run(); // Black Rabbit runs with speed 5. alert(Rabbit.planet); // Earth
5
クラス側の解説 extendすることで、 Rabbitクラス(関数ともいう)の[[prototype]]がAnimalクラス(関数)になる。 extendsはコンストラクタ関数間のプロトタイプチェーンの掲載 (つまるところ関数オブジェクトのプロパティの継承) とプロトタイプ間のチェーン形成 インスタンス→Rabbit.prototype → Animal.prototype を行う class Animal {} class Rabbit extends Animal {} // 静的 alert(Rabbit.__proto__ === Animal); // true // 通常のメソッド alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
6
内部インタフェース: クラスの他のメソッドからアクセス可能だが、外側からはアクセスできないメソッドやプロパティ。 外部インタフェース: 外部のクラスからもアクセス可能なメソッドやプロパティ。 コーヒーメーカーで例えるなら、内部に隠されているもの: ボイラーチューブや発熱体など、は内部インタフェースです。 内部インタフェースはオブジェクトが機能するために使われ、その構成要素はお互いに使用されます。例えば、ボイラーチューブは発熱体に取り付けられます。 しかし、コーヒーメーカーの外側からは、誰もそこに届かないよう保護カバーで閉ざされています。 内部の詳細は隠されており、アクセスできません。私たちは、外部インタフェースを介してのみその機能を利用できます。
7
外部インターフェース 内部でどのように動いているか完全に分からないかもしれませんが、問題ありません
8
パブリック(public): どこからでもアクセス可能です。これらは外部インタフェースになります。 今まで、私たちはパブリックなプロパティとメソッドのみを使用していました。 プライベート(private): クラス内部からのみアクセスできます。これらは内部インタフェース用です。 プロテクト(protected)” : これは、クラス及び、そのクラスを継承したサブクラスの内部からのみアクセス可能であることを意味します。これも内部インタフェースには役立ちます。通常は、継承しているクラスを適切に拡張できるよう、それらにアクセスさせたいため、ある意味ではプライベートよりも広く知られています。 ※ちなみにJavaScriptでは実装されてないので、言語レベルで制御はされず、運用としての実装になる。。
9
_をつける。 class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { if (value < 0) { value = 0; } this._waterAmount = value; } get waterAmount() { return this._waterAmount; } constructor(power) { this._power = power; } } // コーヒーメーカーを生成 let coffeeMachine = new CoffeeMachine(100); // 水を追加 coffeeMachine.waterAmount = -10; // _waterAmount は -10 ではなく、 0 になります
10
セッターはなしで、ゲッターだけ利用するようにすれば読み取り専用になる。 例:powerプロパティが読み取り専用 class CoffeeMachine { // ... constructor(power) { this._power = power; } get power() { return this._power; } } // コーヒーメーカーを作成 let coffeeMachine = new CoffeeMachine(100); alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W coffeeMachine.power = 25; // Error (setter はないので) プロパティフラグとかでも同じことできるけどこっちのほうがらく
11
100が出力される。 protectedは言語レベルでサポートされてない。 powerプロパティで操作してね!お願い!ってことやね。
12
プライベートは # から始める必要があります。それらはクラス内部からのみアクセス可能です。 例えば、ここではプライベートな #waterLimit プロパティを追加し、水量をチェックするロジックを別のメソッドに抜き出しています: また、メソッドもプライベートにしている。 class CoffeeMachine { #waterLimit = 200; #fixWaterAmount(value) { if (value < 0) return 0; if (value > this.#waterLimit) return this.#waterLimit; } setWaterAmount(value) { this.#waterLimit = this.#fixWaterAmount(value); } } let coffeeMachine = new CoffeeMachine(); // can't access privates from outside of the class coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error ちなみにクロージャとかweakmapを使うと プライベートなプロパティを実装できるっぽいけど上記の方が楽!!
13
なにもしなければパブリック。 プライベートフィールドはパブリックなものと衝突しません。 プライベートな #waterAmount とパブリックな waterAmount フィールド両方を同時にもつことができます。 例えば、#waterAmount のアクセサとなる waterAmount を作りましょう。: class CoffeeMachine { #waterAmount = 0; get waterAmount() { return this.#waterAmount; } set waterAmount(value) { if (value < 0) value = 0; this.#waterAmount = value; } } let machine = new CoffeeMachine(); machine.waterAmount = 100; alert(machine.#waterAmount); // Error
14
プライベートは継承先でも使えない プロテクテドならいける。
15
protected privateは新しめの機能なので、ブラウザによってはサポートされてなかったりするし、 継承先で使えないという制限は厳しすぎなので、protectedがちょうどいい。
16
this['#name'] は期待通り動作しません。 試したらundefined がかえってきた。 this.#nameはいける。 Private フィールドは特別です。 これは private であることを維持するための、構文上の制限になります。 外側から使われないから内部で気をつけるだけだけど 文字列でアクセスできないのはどうなんだろな Object.entries(obj)とかでプロパティ情報とってきて以下のようにクローンしようとしてもプライベートなプロパティはコピーできないかも? obj[key] = value; いやそもそも外部からアクセスできないか
17
カプセル化
18
1 外部から変更されずらくする。 外部からの変更を意図していないものが勝手に変更された場合、結果は予測不可能です。 2 利用者へ通知せず、内部のコード変更が容易 もし内部インタフェースを厳密に区切ると、クラスの開発者は利用者へ通知しなくても内部のプロパティとメソッドを自由に変更することができます。 3 ドキュメント化しやすい。 実装の詳細が隠されていて、シンプルかつ良くドキュメント化された外部インタフェースが利用可能であることは常に便利です。
19
戻り値のインスタンスのクラスはpowerArrayとなる。 Arrayのメソッドなのに、Arrayのインスタンスがかえってくるわけではない! よって????の部分はエラーにはならず処理が動き、emptyどないので、falseとなる。 isEmptyメソッドはクラスメソッド(プロトタイプメソッド)なので、 つまり、返却されたオブジェクトの[[prototype]]がPowerArray.prototype のまま変わってないということがわかる。 なぜこういう動きとなるのか次問題にて解説
20
実行コンテキストとなるオブジェクトのconstructorプロパティを利用して、インスタンスしているから。 また、実行コンテキストのconstructorプロパティとは、実行コンテキストであるオブジェクトのクラス(コンストラクタ関数)のことである。 前問の例では以下の通りとなる。 arr.constructor === PowerArray;
21
特別な静的な getter Symbol.species を追加することができます。 getter Symbol.species では、利用するコンストラクタ関数を指定します。 これがあると、filterやmap関数は↑のゲッター関数で指定されたコンストラクタ関数を利用して値を返すようになる! なので、Array型を返したい場合は Arrayクラス=コンストラクタ関数なので、これを指定すればよい。 class PowerArray extends Array { isEmpty() { return this.length === 0; } // 組み込みのメソッドは、これをコンストラクタとして使います!! static get [Symbol.species]() { return Array; } } let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false // filter はコンストラクタとして arr.constructor[Symbol.species] を使って新しい配列を作ります let filteredArr = arr.filter(item => item >= 10); // filteredArr は PowerArray ではなく Array です alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function また、Map や Set などの他のコレクションも同様に動作する。 これらも Symbol.species を使用しています。
22
通常のクラス同士の継承は、 コンストラクタ関数自体のプロトタイプの継承と コンストラクタ関数.プロトタイプオブジェクトの継承の二つがあった。 だけど組み込み同士の場合 コンストラクタ関数自体のプロトタイプの継承がない。 なので、静的なフィールドは継承されない。 まあ確かにいらないか
23
それはある。試したら動いたので。 組み込み同士がないだけ。
24
左辺のオブジェクトが右辺のクラス(コンストラクタ関数)に属するかチェックする時に利用する演算子。 継承も考慮される。 構文は次の通りです: obj instanceof Class それは obj が Class (または、それを継承しているクラス)に属している場合に true を返します。 class Rabbit {} let rabbit = new Rabbit(); // Rabbit クラスのオブジェクト? alert( rabbit instanceof Rabbit ); // true
25
当然コンストラクタ関数もいける。 class の代わり function Rabbit() {} alert( new Rabbit() instanceof Rabbit ); // true また Array のような組み込みクラスでも動作します。: let arr = [1, 2, 3]; alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true
26
左辺のオブジェクトのプロトタイプチェーンのうち、 右辺のClass.prototypeとマッチするかチェックしている。 言い換えると、以下のような比較を行います: obj.__proto__ == Class.prototype obj.__proto__.__proto__ == Class.prototype obj.__proto__.__proto__.__proto__ == Class.prototype ... // いずれかが true の場合、 true が返却されます // そうでない場合、チェーンの末尾に到達すると false を返します
27
右辺のクラス側に、 静的メソッド [Symbol.hasInstance](obj)を追加した場合、比較時、このメソッドが呼ばれるようになる。 そのメソッドの引数は左辺のオブジェクトであり、trueを返せば、プロトタイプチェーンのチェックをせずに戻り値をtrueとすることができる。 例えば、以下では、オブジェクトにとあるプロパティが存在していれば、継承関係なく、trueとみなしてしまっている。 // catEat プロパティをもつものは animal と想定する // instanceOf チェックを設定 class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj)が呼ばれます
28
objA.isPrototypeOf(objB)はobjAがobjBのプロトタイプチェーンに含まれるかチェックなので、 いいかえると Class.prototype.isPrototypeOf(obj) になる。 オブジェクトでもいけるけど、 プロトタイプ側が利用するメソッドだとと覚えておこう! Class.prototypeがobjのプロトタイプチェーンであるか? っていみ
29
[object Object]
30
だいたいtoStringは配列とかだったら要素が出るようにArray.prototype でオーバーライドされてカスタマイズされている。 しかしObject.prototype.toString をArrayのインスタンスを実行コンテキストとして呼び出すとカスタマイズされてない処理が行われる。 数値の場合、それは [object Number] になります。 真偽値の場合、[object Boolean] になります。 null の場合: [object Null] undefined の場合: [object Undefined] 配列の場合: [object Array] …など (カスタマイズ可能). デモを見てみましょう: // 使いやすくするために toString メソッドを変数にコピー let objectToString = Object.prototype.toString; // これの型はなに? let arr = []; // call関数を使用している。 alert( objectToString.call(arr) ); // [object Array]
31
特別なオブジェクトプロパティ Symbol.toStringTag を使用してカスタマイズできます。 例: let user = { [Symbol.toStringTag]: 'User' }; //Object.prototype.toStringを短縮してかいてる alert( {}.toString.call(user) ); // [object User] // user.toString()でも変わんなさそう // 環境固有のオブジェクトとクラスのtoStringTag: alert( window[Symbol.toStringTag]); // window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest ほとんどの環境固有のオブジェクトには、このようなプロパティがあります。これはいくつかのブラウザ固有の例です。: alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
32
型やクラスのチェックメソッド
33
typeof 演算子 プリミティブ型が対象 console.log(typeof 42); // Expected output: "number" {}.toString メソッド プリミティブ, 組み込みオブジェクト, Symbol.toStringTag をもつオブジェクト が対象 instanceof 演算子 オブジェクトが対象 ご覧のように、{}.toString は技術的には “より高度な” typeof です。 そして、instanceof 演算子は、クラス階層を扱っていて継承を考慮したクラスのチェックをしたい場合に本当に輝きます。
34
aはたしかにBコンストラクタ関数によってインスタンスされたオブジェクトではない。 しかし、内部的には a instanceof B の処理は aのプロトタイプチェーンがB.prototype に属しているかチェックしている。 a.__proto__ はA.prototypeである。 また、A.prototype==B.prototype となるように同じ空のオブジェクトを代入していることから a.__proto__ == B.prototype が成り立つ。
35
インスタンスメソッドなので、User.prototypeに対し、 Object.assignでメソッドをアサインさせてやれば良い // メソッドをコピー Object.assign(User.prototype, sayHiMixin); // これで User は sayHi できます new User("Dude").sayHi(); // Hi Dude!
36
継承は一つのオブジェクトからのみ行えるが、 複数のオブジェクトから継承したいってときに利用できる。 JavaScriptは多重継承をサポートしていませんが、Object.assignなどでプロトタイプにプロパティをコピーすることでミックスインが実装できます。
37
まず登録する関数はどのように管理するか? _eventHandersオブジェクトを定義し、 プロパティにはeventNameをもつようにする。 各eventNameプロパティの値には関数をリスト形式で管理する。 要するに以下のように登録できる。 _eventHanders[evemtName] = 呼び出すハンドラのリスト(一つのイベントネームに対し、複数ハンドラを登録可能!) onメソッドは以上の登録処理を行い offメソッドはハンドラのリストから対象ハンドラを削除する。 トリガ機能は、 eventNameを引数にとり、管理している関数を呼び出す。 ハンドラ呼び出す際の引数の渡せるようサポートする。 trigger(eventName, ...args) 答えは以下 let eventMixin = { /** * イベントの購読, 使い方: * menu.on('select', function(item) { ... } */ on(eventName, handler) { if (!this._eventHandlers) this._eventHandlers = {}; if (!this._eventHandlers[eventName]) { this._eventHandlers[eventName] = []; } this._eventHandlers[eventName].push(handler); }, /** * 購読のキャンセル 使い方: * menu.off('select', handler) */ off(eventName, handler) { let handlers = this._eventHandlers && this._eventHandlers[eventName]; if (!handlers) return; for(let i = 0; i < handlers.length; i++) { if (handlers[i] == handler) { handlers.splice(i--, 1); } } }, /** * イベントを生成してデータをアタッチ * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { if (!this._eventHandlers || !this._eventHandlers[eventName]) { return; // イベントに対応するハンドラがない場合 } // ハンドラ呼び出し this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } }; つまるところミックスインというのは処理の骨格を実装できる。 それを扱うクラスはそのミックスインした処理をラップしたりして扱うことができる。
38
setTimeoutは非同期なので、キャッチされない。 スケジュールされた関数の内側の例外をキャッチするためには、その関数の中に try..catch が必要です。: setTimeout(function() { try { noSuchVariable; // try..catch がエラーをハンドリングします! } catch (e) { alert( "error is caught here!" ); } }, 1000);
39
name エラー名です。未定義変数の場合、それは "ReferenceError" です。 message エラー詳細に関するテキストメッセージです。 ほとんどの環境では、その他非標準のプロパティが利用可能です。最も広く使われ、サポートされているのは以下です: stack※非標準 現在のコールスタックです: エラーに繋がったネスト呼び出しのシーケンスに関する情報を持つ文字列です。デバッグ目的で使われます。 try { lalala; // エラー, 変数が宣言されていません! } catch(err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...スタック呼び出し) // 全体としてエラーを表示する事もできます // エラーは "name: message" として文字列に変換されます alert(err); // ReferenceError: lalala is not defined }
40
// 全体としてエラーを表示する事もできます // エラーは "name: message" として文字列に変換されます alert(err); // ReferenceError: lalala is not defined
41
エラーの詳細が必要ない場合、catch はそれを省略できます: try { // ... } catch { // <-- (err) なし // ... }
42
潜在的なエラーが見込まれる場合 例えば、ネットワーク経由でサーバまたは別のソースから受信したデータをデコードするために JSON.parse(受信データ)を利用したときなど。 受診データが不正であった場合のエラーをキャッチできる。 let json = "{ bad json }"; try { let user = JSON.parse(json); // <-- エラーが起きたとき... alert( user.name ); // 動作しません } catch (e) { // ...実行はここに飛びます alert( "Our apologies, the data has errors, we'll try to request it one more time." ); alert( e.name ); alert( e.message ); }
43
throw 演算子はエラーを生成します。 構文は次の通りです: throw <error object>
44
技術的には、エラーオブジェクトとしてなんでも使うことができます。たとえ、数値や文字列のようなプリミティブでもOKです。しかし、name と message プロパティを持つオブジェクトを使うのがベターです(組み込みのエラーと互換性をいくらか保つために)。 通常は組み込みの Error クラスを継承しているエラーオブジェクトがよい! →instanceof演算子で判断利用が可能のため
45
Error, SyntaxError, ReferenceError, TypeError などです。我々もエラーオブジェクトを作るのにそれらが使えます。 構文は次の通りです: let error = new Error(message); // or let error = new SyntaxError(message); let error = new ReferenceError(message); // ... 組み込みのエラー(任意のオブジェクトではなく、エラーのみ)では、name プロパティはコンストラクタの名前と全く同じになります。そして message は引数から取られます。 例: let error = new Error("Things happen o_O"); alert(error.name); // Error alert(error.message); // Things happen o_O
46
プロパティがあることを期待しているのに、ない場合はSyntaxErrorでいいかも? リファレンスエラーは明示的に投げることなさそう JSON.parseが生成するエラーはSyntaxError です。 try { JSON.parse("{ bad json o_O }"); } catch(e) { alert(e.name); // SyntaxError alert(e.message); // Unexpected token o in JSON at position 0 } そして、我々のケースでは、ユーザは必ず "name" を持っていると仮定するので、name の欠落もまた構文エラーとして扱います。 なので、それをスローするようにしましょう: let json = '{ "age": 30 }'; // 不完全なデータ try { let user = JSON.parse(json); // <-- エラーなし if (!user.name) { throw new SyntaxError("Incomplete data: no name"); // (*) } alert( user.name ); } catch(e) { alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name }
47
全てのエラーを黙殺させてしまうため、 想定してないエラーも握りつぶされてしまう可能性がある。 キャッチは想定していたエラーだけを処理し、想定してないオブジェクトは“再スロー” するべき let json = '{ "age": 30 }'; // 不完全なデータ try { let user = JSON.parse(json); if (!user.name) { throw new SyntaxError("Incomplete data: no name"); } blabla(); // 予期しないエラー alert( user.name ); } catch(e) { if (e.name == "SyntaxError") { alert( "JSON Error: " + e.message ); } else { throw e; // 再スロー (*) } }
48
finally を持つ場合があります。 もし存在する場合、それはすべてのケースで実行します。: 拡張された構文はこのようになります。: try { ... コードを実行しようとします ... } catch(e) { ... エラーを処理します ... } finally { ... 常に実行します ... }
49
finally 句は try..catch の前に何かを開始して、どのような結果であれファイナライズをしたいときに頻繁に使われます。 例えば計測に関しては、正常ケースでも異常ケースでも、最後に計測完了の処理を行う必要がある。 その際にfinallyがつかえる
50
エラーはキャッチせず、投げられっぱなしになる。→上位側でキャッチ可能、キャッチしない場合はいつもの感じのエラーとなる。 ただし構文の外に出る前にfinallyが実行される。 catch 句がない try..catch 構造も役立ちます。私たちはここでエラーを正しく処理したくないが、開始した処理が完了したことを確認したいときに使います。 function func() { // (計測など)完了させる必要のあるなにかを開始する try { // ... } finally { // すべてが死んでいても完了させる } } 上のコードでは、try の内側のエラーは常に抜けます。なぜなら catch がないからです。しかし finally は実行フローが外部に移る前に機能します。
51
ブラウザでは関数を特別な window.onerror プロパティに代入することができます。それはキャッチしていないエラーの場合に実行されます。 構文: window.onerror = function(message, url, line, col, error) { // ... }; message エラーメッセージ url エラーが起きたスクリプトのURL line, col エラーが起きた行と列番号 error エラーオブジェクト 例: <script> window.onerror = function(message, url, line, col, error) { alert(`${message}\n At ${line}:${col} of ${url}`); }; function readData() { badFunc(); // おっと、何かがおかしいです! } readData(); </script> グローバルハンドラー window.onerror の役割は、通常スクリプトの実行の回復ではありません – プログラミングエラーの場合、恐らくそれは不可能なので開発者にエラーメッセージを送ります。
52
finallyはtryやcatchブロック内からの他の処理への飛び出しがあったとしても、その前に実行される。 finallyは最後に実行する保証がある。 その違いは、関数内のコードを見ると明らかになります。 もし try..catch の “飛び出し” がある場合、振る舞いは異なります。 例えば、try..catch の中で return がある場合です。finally 句は try..catch が どのような終わり方の場合にでも 動作します。たとえ、return 文経由でさえも。 function f() { try { alert('start'); return "result"; } catch (e) { /// ... } finally { alert('cleanup!'); } } f(); // cleanup! …もしくは次のように throw がある場合: function f() { try { alert('start'); throw new Error("an error"); } catch (e) { // ... if("can't handle the error") { throw e; } } finally { alert('cleanup!') } } f(); // cleanup! ここで finally はクリーンアップを保証します。もし f の終わりにコードをおいた場合は実行されない場合があります。
53
エラーは message, name 、望ましくは stack のような基本のエラープロパティをサポートすべきです。 ですが、他にも独自のプロパティを持つかもしれません。例えば HttpError オブジェクトであれば、 404, 403 もしくは 500 といった値をとる statusCode プロパティです。 JavaScript は任意の引数で throw できるので、技術的にはカスタムのエラークラスは Error から継承する必要はありません。 しかし、継承しているとエラーオブジェクトを識別する obj instanceof Error を使えるようになります。そのため、継承しておくほうのがベターです。 アプリケーションを開発するにつれ、独自のエラーが自然に階層を形成します。たとえば、 HttpTimeoutError は HttpError を継承する、といったように。
54
カスタムエラーを作成する。 データのチェック(バリデート)でエラーがおきたので、バリデーションエラーとする。 class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } // Usage function readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new ValidationError("No field: age"); } if (!user.name) { throw new ValidationError("No field: name"); } return user; } // try..catch での動作例 try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { alert("Invalid data: " + err.message); // Invalid data: No field: name } else if (err instanceof SyntaxError) { // (*) alert("JSON Syntax Error: " + err.message); } else { throw err; // 知らないエラーなので、再スロー (**) } }
55
プロパティが存在しなかったり、誤ったフォーマット(age が文字列値のような)など。ここで、存在しないプロパティに対するより具体的なクラス PropertyRequiredError を作りましょう。欠落しているプロパティについての追加の情報を保持します。 class PropertyRequiredError extends ValidationError { constructor(property) { super("No property: " + property); this.name = "PropertyRequiredError"; this.property = property; } }
56
バリデーションエラークラスなど、上位エラーオブジェクトでthis.constructor.name;を利用する。 ↑これはオブジェクトの[[prototype]]のもちもののプロパティである。 class ValidationError extends Error { constructor(message) { super(message); this.name = this.constructor.name; } }
57
例外を扱う際の考え方として、 例外を出す外側で例外のチェックをする場合は、エラーとしての考え方もより抽象的である方がよい。 例えばreadUserは ValidationErrorやSyntaxErrorを出すが、 これは外側の上位レベルでは、あまり関心のない出来事である。 抽象度を上げて考えると、ただ読み込みに失敗したとだけわかればよい。 そのため、上位レベルではエラーも同様に抽象化するべきである。
58
例外のラッピングと言われるテクニック 汎用的な “データ読み込み” エラーを表現する新しいクラス ReadError を作り、 ValidationError や SyntaxError が内部で発生したらReadErrorを作り再スローする。 ReadError オブジェクトは自身の cause プロパティに元のエラーへの参照を保持させておく。 以下全容 class ReadError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = 'ReadError'; } } class ValidationError extends Error { /*...*/ } class PropertyRequiredError extends ValidationError { /* ... */ } function validateUser(user) { if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } } function readUser(json) { let user; try { user = JSON.parse(json); } catch (err) { if (err instanceof SyntaxError) { throw new ReadError("Syntax Error", err); } else { throw err; } } try { validateUser(user); } catch (err) { if (err instanceof ValidationError) { throw new ReadError("Validation Error", err); } else { throw err; } } } try { readUser('{bad json}'); } catch (e) { if (e instanceof ReadError) { alert(e); // Original error: SyntaxError: Unexpected token b in JSON at position 1 alert("Original error: " + e.cause); } else { throw e; } } 外部のコードは instanceof ReadError をチェックするだけですんだ!
59
例えばスクリプトとモジュールの読み込みです 指定された src でスクリプトをロードする関数 loadScript(src) を見てください。: function loadScript(src) { // <script> タグを作りページに追加します // 指定された src のスクリプトの読み込みを開始し、完了時に実行します let script = document.createElement('script'); script.src = src; document.head.append(script); } これは、指定された src をもつ、新たに動的に作成された <script src="…"> をドキュメントに挿入します。ブラウザは自動的に読み込みを開始し、完了時に実行します。 使用例: // スクリプトを読み込み、実行する loadScript('/my/script.js'); スクリプトは “非同期に” 実行され、すぐに読み込みを開始しますが、後で(関数がすでに終了した後)実行されます。
60
コールバック処理を実装する! loadScript に2つ目の引数に、スクリプトが読み込まれたときに実行する callback 関数を追加しましょう: ちょっと難しいがスクリプトの読み込みが完了したらonloadイベントがはしるため、そこに関数を登録しておくことで、コールバック処理を実装できる。 function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(script); document.head.append(script); } loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { alert(`Cool, the ${script.src} is loaded`); alert( _ ); // ロードされたスクリプトで宣言されている関数 });
61
読みこみスクリプトでエラーがあればオンエラーイベントが起動するので、関数を登録する。 正常ケースでも異常ケースでも受け取ったコールバック関数を起動させることで、 コールバック関数内部で 正常ケースと異常ケースを分けて定義させるのがよい。 またJavaScript においてよく使われるテクニックである function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } これは成功時に callback(null, script) を呼び、それ以外の場合には callback(error) を呼びます。 使用方法: loadScript('/my/script.js', function(error, script) { if (error) { // エラー処理 } else { // スクリプトの読み込みが成功 } });
62
これは “コールバック地獄” や “破滅のピラミッド” と呼ばれます。 Promiseを使えば解決できる。
63
Promiseオブジェクト
64
let promise = new Promise(function(resolve, reject) { // executor });
65
コンストラクタの引数にはexecutor関数を定義する。 new promise が作成されると、executor は自動的に実行されます。 executorの実行部では、 非同期メソッドを記述し、 JavaScript側から提供される関数 (executor関数実行時、Promseのコンストラクタから渡されてそう。) resolve, rejectを呼び出すことによって、 promiseオブジェクトのstateの管理を行うことができる。 例えばsetTimeoutでresolve 前問であったようなスクリプト読み込みにおける、onload イベントにてresolve
66
resolve関数は resolve(value)で呼び出す。 ジョブが正常に終了した場合。結果は valueになる。 reject関数は reject(error)で呼び出す。 エラーが発生した場合、error はエラーオブジェクトを指定する。 →これらは、promiseのresultに登録され、コールバック関数でうけとれる。 つまり正常終了とエラーの判断は、 executor関数内で自分で定義する
67
state – 最初は "pending(保留中)" であり、その後 resolve が呼ばれると "fulfilled(完了)" 、もしくは reject が呼ばれると "rejected(拒否)" に変更されます。 result – 初期値は undefined です。その後、resolve(value) が呼ばれると value に、reject(error) が呼ばれると error になります
68
成功ならstate:”fulfilled” result:value(resolve呼ぶ時のパラメータ) 失敗ならstate:”rejected” result:error(reject呼ぶ時のエラーオブジェクト) 例 let promise = new Promise(function(resolve, reject) { // promise が作られたとき、関数は自動的に実行されます // 1秒後、ジョブが "done!" という結果と一緒に完了したことを合図します setTimeout(() => resolve("done!"), 1000); });
69
.then, .catch, .finally を使って、promiseの結果をうけてから、処理をはじめることがきます。サブスクライブするともいう。 これらは即pendingのプロミスを返すため、以降のコードはそのまま動いていく。 サブスクライブした処理は、 promiseの状態次第でコールバック処理が行われる。 正確には、 promise が pending の場合、.then/catch/finally ハンドラは結果を待ちます。 promiseのstateが変わったら、現在の処理が完了後に処理されるようになる。
70
thenはコールバックを登録するだけで、 待つわけではないので注意!! then() メソッドは常に新しい Promise インスタンスを返す なのでプロミスチェーンのthenは最後までしっかり実行される。 コールバックの登録のみ行われ、それぞれプロミスの状態が変わり次第の実行となる! thenには成功用とエラー用の二つの関数をパラメータとしてわたし、登録できる promise.then( function(result) { /* 成功した結果を扱う */ }, function(error) { /* エラーを扱う */ } ); .then の最初の引数の関数は、promise が解決(resolve)されたときに実行され、resolve(value)のvalueを受け取ります。 .then の2つ目の引数の関数は、promise が拒否(reject)されたときに実行され、reject(エラーオブジェクト) のエラーオブジェクトを受け取ります。 例えば、これは正常に解決された promise の動きです: let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve("done!"), 1000); }); doneの値が下のresultに設定される! // resolve は .then の最初の関数を実行する promise.then( result => alert(result), // 1秒後に "done!" を表示 error => alert(error) // 実行されない ); もし、正常完了の場合だけを扱いたい場合は、.then には引数を1つの関数だけを指定します: let promise = new Promise(resolve => { setTimeout(() => resolve("done!"), 1000); }); promise.then(alert); // 1秒後に "done!" を表示
71
エラーにのみ関心がある場合は、 .catch(function) が使えます。 .then(null, function) と全く同じ。 let promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Whoops!")), 1000); }); // .catch(f) は promise.then(null, f) と同じです promise.catch(alert); // 1秒後に "Error: Whoops!" を表示 .catch(f) の呼び出しは、.then(null, f) の完全な類似物であり、単に簡略化したものです。
72
正常でもエラーでも呼び出されるし、 引数なしのハンドラを登録しとけばいいので、なんかの処理のファイナライザをかくのに最適 あとプロミスオブジェクトはおなじのをかえす .finally(f) 呼び出しは、Promise が完了したとき(解決または拒否になったとき)に必ず実行されるという意味で .then(f, f) に似ています。 finally はクリーンアップを実行するのに便利なハンドラです。例えば、結果がどちらであっても、もう不要となったので読み込み中のインジケータを停止するなどです。 finallyに登録するハンドラは引数なし。 このようになります: new Promise((resolve, reject) => { /* 時間のかかる処理を行い、その後 resolve/reject を呼び出す */ }) // 成功か失敗かは関係なく、promise が確定したときに実行されます .finally(() => 読込中のインジケータを停止する ) // したがって、読み込み中のインジケータは結果/エラーを処理する前に必ず停止されます .then(result => 結果を表示する, err => エラーを表示する) 重要! finallyはpromiseオブジェクトをそのまま引き継いで返すので、上記の例の通り.thenや.catchを実行できることを認識せよ! また上記例のfinallyやthenは、待つわけではなく購読するだけで、現在のコード実行はおこなっていく。 なのでfinallyの処理が完了したらthenが動くように購読されている。 finally は promise の結果を処理する手段ではないので、これは非常に便利です。
73
Settledという
74
関数はpromiseオブジェクトを返すことで、 非同期処理を管理でき、.thenメソッドが使用可能となる。 スクリプトの読み込みやエラーがあったところで、コールバック関数をよんでいたが、resolve, rejectを呼んで、ステータスを変更するだけでよい。 function loadScript(src) { return new Promise(function(resolve, reject) { let script = document.createElement('script'); script.src = src; script.onload = () => resolve(script); script.onerror = () => reject(new Error("Script load error: " + src)); document.head.append(script); }); }
75
loadScriptをよぶことで、promiseオブジェクトを作成し、 thenメソッドにて、成功時、エラー時の処理を購読! 以下例のように複数のハンドラを定義可能 let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"); promise.then( script => alert(`${script.src} is loaded!`), error => alert(`Error: ${error.message}`) ); promise.then(script => alert('Another handler...'));
76
Promiseは使いやすい。 Promise では .then を何度でも呼び出すことができ、ハンドラを複数登録できる。
77
以下無名関数とほぼおなじ。 promise.then( result => alert(result), ); alert関数は引数をとり、その引数にはvalueが入ってくるので、alert関数だけでもそりゃ動く。
78
promise.thenによるチェーン構造のことである。 thenメソッドは、promiseオブジェクトを返すので、チェーン化できる。 最初はpending の状態のpromiseである。 購読した関数の処理が全部終わると、 thenメソッドで作成した promiseが更新される。(status:resolvedとして)、返却した値はresultに設定される。 thenにて作成されるプロミスのかわりに、プロミスを返却することもできる。 なので、次の .then はその結果と一緒に呼ばれます。 new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); // (*) }).then(function(result) { // (**) alert(result); // 1 return result * 2; }).then(function(result) { // (***) alert(result); // 2 return result * 2; }).then(function(result) { alert(result); // 4 return result * 2; }); ただ、thenチェーンで非同期メソッドを利用してないので、あんまこれは使われないかも
79
技術的には単一の Promise に複数の .then を追加することもできます。これはチェーンではありません 例: let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); ここで行ったことは、1つの promise に対して複数のハンドラの設定です。これらは結果を相互に渡しません。代わりにそれぞれが独立して処理をします。
80
ハンドルにて返す値は プリミティブだけでなく promiseかthenableが指定できる。 promiseかthenableを返すと promiseなら非同期処理の状態と結果が thenで作成されてたpromise に登録される。 thenableならthenメソッドに定義したexecutorが実行され、状態と結果が thenで作成されてたpromise に登録される。 ※thenは同期的にpendingのpromiseオブジェクトを返すので、そのpromiseを更新する感じ。 new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }).then(function(result) { alert(result); // 1 return new Promise((resolve, reject) => { // (*) setTimeout(() => resolve(result * 2), 1000); }); }).then(function(result) { // (**) alert(result); // 2 return new Promise((resolve, reject) => { setTimeout(() => resolve(result * 2), 1000); }); }).then(function(result) { alert(result); // 4 }); 1秒たったら処理して1秒たったら処理してみたいな感じ
81
“thenable” オブジェクトという、メソッド .then をサポートしたオブジェクトを返すことがある。 thenableのthenメソッドでは、 非同期処理を記述でき、resolve,reject引数をとり、コールバックとして任意のタイミングで呼び出しできるので、 promiseオブジェクトで定義していた、executorなどの処理と同等の内容を記載でき、代わりとして利用できる。 thenでthenableを返すと内部のほうでthenの処理が実行され、結果が作成されていたpromiseに登録される。 thenを実装しているからといって、そのthenメソッドをこちらで直接呼ぶわけではなく、組み込み処理によって呼ばれて管理されている。 既存のクラスに対して、thenメソッドを付与するだけで使えるのが利点。 また、プロパティや外部変数も利用できるのがメリット 以下のとおり new Promise(resolve => resolve(1)) .then(result => { return new Thenable(result); // (*) }) .then(alert); // 1000ms 後に 2 を表示 この思想は、サードパーティライブラリが彼ら自身の “promise 互換な” オブジェクトを実装できるというものです。それらは拡張されたメソッドのセットを持つことができ、promiseとも互換がある。 Thenableの作り方は以下のとおり class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // function() { native code } // 1秒後に this.num*2 で resolve する setTimeout(() => resolve(this.num * 2), 1000); // (**) } }
82
aaaaaaが優先されて表示され、 次にasが表示される。 やっぱexecuterは同期的に動く
83
thenに一つは非同期アクションを含めて、 promiseかthenableで返すこと。 thenの中で同期的な処理しかしないなら別のthenや他のコードに混ぜれると思う
84
フェッチからjson化の流れが二回あるのでloadJson として共通化できる。 thenメソッドの処理を戻り値としてるので、プロミスがかえる function loadJson(url) { return fetch(url) .then(response => response.json()); } loadJsonをラップするGitHub用のユーザ取得メソッドを定義(ここはやらなくてもいいが、やったほうが綺麗な気がする) function loadGithubUser(name) { return loadJson(`https://api.github.com/users/${name}`); } ↑で十分再利用関数として定義できた。 ついでに その後のアバター画像表示処理はそのまま関数宣言におきかえる function showAvatar(githubUser) { return new Promise(function(resolve, reject) { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); }, 3000); }); } 利用する時はこんな感じになる 3000); }); } // Use them: loadJson('/article/promise-chaining/user.json') .then(user => loadGithubUser(user.name)) .then(showAvatar) .then(githubUser => alert(`Finished showing ${githubUser.name}`)); // ... ↑見やすくなった!!
85
promise が reject されると、制御は最も近い reject ハンドラに移ります。この動きは実際に非常に便利です。 ご覧の通り、.catch は直後である必要はありません。1つまたは複数の .then の後に現れるかもしれません。 なので、上の例でいうと、 fetch('https://no-such-server.blabla') // この処理でrejects される .then(response => response.json())←のハンドラ実行はスキップされる。 また、サイトはすべて問題ありませんが、レスポンスが有効な JSON でない可能性もあります。すべてのエラーをキャッチする最も簡単な方法はチェーンの末尾に .catch を追加することです。 fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(response => response.json()) .then(githubUser => new Promise((resolve, reject) => { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); }, 3000); })) .catch(error => alert(error.message)); 通常、この .catch は呼ばれません。ですが、上の promise のいずれかがreject した場合(ネットワーク問題 or 無効な json など)、それをキャッチします。
86
executor と promise ハンドラのコードは 見えないtry catchをもっており、投げられたエラーは勝手にrejectしたものとして扱ってくれる! ↑既存コードを変えなくていいので便利!! ただし注意! 暗黙的やtry catch をもっているということで、try の中でのエラーしか扱ってくれない ! どういうことかというと、 非同期の処理はtry内の構文でエラーが発生しても、エラー発生時はtry構文外となってしまう。 →setTimeoutでエラーを発生させたとしても、エラーはキャッチされない。 なので、同期的なエラーしか自動的にrejectされたものとして扱ってくれないので注意 これは次のと同じように動作します: new Promise(function(resolve, reject) { reject(new Error("Whoops!")); }).catch(alert); // Error: Whoops! executor にある "見えない try..catch" はエラーを自動的にキャッチし reject された promise として扱っています。 これは executor だけでなくハンドラの中でも同様です。.then ハンドラの中で throw した場合、promise の reject を意味するので、コントロールは最も近いエラーハンドラにジャンプします。 ここにその例があります: new Promise(function(resolve, reject) { resolve("ok"); }).then(function(result) { throw new Error("Whoops!"); // promise を rejects }).catch(alert); // Error: Whoops! また、これは throw だけでなく同様にプログラムエラーを含む任意のエラーに対してです: new Promise(function(resolve, reject) { resolve("ok"); }).then(function(result) { blabla(); // このような関数はありません }).catch(alert); // ReferenceError: blabla is not defined 最後の .catch は明示的な reject だけでなく、上記のハンドラのような偶発的なエラーもキャッチします。
87
キャッチされてから、promiseが戻り、 そのままthenの処理が行われる。 .catch の中で throw する場合、制御は次の最も近いエラーハンドラに移ります。 そして、エラーを処理して正常に終了すると、次に最も近い成功した .then ハンドラに続きます。
88
thenのここうごく??のところは動かず、スルーされる、 エラーの場合は一番近いcatchがうごく!
89
いったん同期的な処理が全部実行される。 そのあとマイクロタスクキューをさがしてキャッチされてないなーってわかれば、 スタックされたままのrejectとなり、通常のエラーとして扱われ、コンソールでエラーが出力される。 しかし、非同期処理がエラーになるだけなので処理落ちはしないはず エラーの場合、promise は “rejected” になり、実行は最も近い reject ハンドラにジャンプします。ですが、上の例にそのようなハンドラはありません。そのため、エラーは “スタック” します(行き詰まります)。 実際、コード内の通常の未処理のエラーと同様、このような場合は何かが誤っていることを意味します。 通常のエラーが発生し、try..catch でキャッチされない場合何が起こるでしょうか?スクリプトはコンソールにメッセージを表示し終了します。同様のことが、未処理の promise の reject でも発生します。 JavaScript エンジンはこのような reject を追跡し、その場合にはグローバルエラーを生成します。上の例を実行すると、コンソールでエラーを見ることができます。
90
ブラウザでは、イベント unhandledrejection を使ってキャッチできます。: イベントエラーハンドリングされていない Promise が拒否されたときにグローバルスコープに送られるイベントです! window.addEventListener('unhandledrejection', function(event) { // イベントオブジェクトは2つの特別なプロパティを持っています: alert(event.promise); // [object Promise] - エラーを生成した promise alert(event.reason); // Error: Whoops! - 未処理のエラーオブジェクト }); new Promise(function() { throw new Error("Whoops!"); }); // エラーを処理する catch がない このイベントは HTML 標準 の一部です。 エラーが発生し .catch がない場合、unhandledrejection ハンドラが発火し、エラーに関する情報を持っている event オブジェクトが渡ります。なので、その情報を使い、何かをすることができます。 通常、このようなエラーはリカバリ不可なので、最善の方法はユーザにその問題を知らせ、サーバへそのインシデントについて報告することです。
91
解答: いいえ、実行されません: new Promise(function(resolve, reject) { setTimeout(() => { throw new Error("Whoops!"); }, 1000); }).catch(alert); チャプターの中で言った通り、関数コードの周りには "暗黙の try..catch" があります。そのため、すべての同期エラーは処理されます。 しかし、ここではエラーは executor が実行中でなく、その後に生成されます。したがって、promise はそれを処理できません。 通常のエラーとして扱われてしまう。 期待した動作をさせたいなら、setTimeoutで渡す関数の中にtry catch構文をかき、 キャッチしたらrejectするようにせよ。
92
promise.allは同期的に全ての処理完了待つわけではなく、 引数のイテラブルからpromiseを一つにまとめ、新しいpromiseとして管理する。 全てのpromiseが完了したタイミングで、promiseのステータスやリザルト値が更新される。 このpromiseのresultは、結果の配列が設定される。 なので、このpromise にthenなどでコールバックを登録することで、 全ての非同期処理を並列に実行し、全てが完了したタイミングでコールバックを起動することができる! 並列に複数の promise を実行し、すべてが準備できるまで待ちたいとしましょう。 例えば、平行で複数の URL をダウンロードし、すべてが完了したらコンテンツを処理する場合です。 これが Promise.all の目的です。 構文は次の通りです: let promise = Promise.all(iterable); Promise.all は iterable(反復可能, 通常は promise の配列)を取り、新しい promise を返します。 新しい promise は、配列の各 promise がすべて解決され、それらの結果を配列に持つと解決(resolve)されます。
93
並列で動き、三秒後に全て終わる。 promiseがかえってくるので、.thenでresult値をアラートする。 result値は配列となっており、順番も定義順となるので [1,2,3]となる。
94
fetch関数は非同期実行を行う関数でpromiseを返す関数である。 配列をmap関数で、それぞれfetch関数を実行し、promiseの配列を取得する。 その後、Promise.allで確認する。 一般的なやり方は、処理データの配列を promise の配列にマップし、それを Promise.all にラップすることです。 例えば、URL の配列がある場合、次のようにしてすべてをフェッチできます: let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://api.github.com/users/jeresig' ]; // 各 url を promise の fetch(github url) へマップする let requests = urls.map(url => fetch(url)); // Promise.all はすべてのジョブが解決されるまで待ちます Promise.all(requests) .then(responses => responses.forEach( response => alert(`${response.url}: ${response.status}`) ));
95
いずれかの promise が reject された場合、Promise.all により返却された promise は即座にエラーと一緒に reject します。 つまり、Promise.allによって返却されるpromiseはrejectされた状態となる。 また、その他のresolveしたpromiseは無視される。つまり、rejectされた時点で完了をまたなくなる。 そのため、catchすることができる。 && みたいなかんじ 例: Promise.all([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).catch(alert); // Error: Whoops! ここでは、2つ目の promise が2秒後に reject されます。それは即座に Promise.all の reject に繋がるため、catch が実行されます。: reject されたエラーは Promise.all 全体の結果になります。 エラー時の場合, 他の promise は無視されます ある promise が reject がされると、Promise.all は即座に reject し、配列の他の promise を完全に忘れます。これらの結果は無視されます。
96
そのまま戻り値のPromiseオブジェクトのresolveに結果が格納される。 通常、Promise.all(iterable) は promise の iterable (ほとんどの場合は配列)を受け付けます。しかし、もしそれらのオブジェクトが promise ではない場合、Promise.resolve でラップします。 →executorやハンドラでのプリミティブでの返却はラップされる! 例えば、ここでは結果は [1, 2, 3] になります: Promise.all([ new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000) }), 2, // Promise.resolve(2) と扱われる 3 // Promise.resolve(3) と扱われる ]).then(alert); // 1, 2, 3 したがって、必要に応じて準備済みの値を Promise.all に渡せます。
97
Promise.allSettled は結果に関わらずすべての promise が解決するまで待ちます。 戻り値はpromiseで、resultの中身はオブジェクトの配列となる。 →エラーは容認されてしまう! 容認されてもいいなら! そのオブジェクト内容は以下の通り。 成功したレスポンスの場合: {status:"fulfilled", value:result} エラーの場合: {status:"rejected", reason:error} 例えば、複数のユーザに対する情報をフェッチしたいとします。たとえ1リクエストが失敗しても、他の結果はほしいです。 Promise.allSettled を使いましょう: let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://no-such-url' ]; Promise.allSettled(urls.map(url => fetch(url))) .then(results => { // (*) results.forEach((result, num) => { if (result.status == "fulfilled") { alert(`${urls[num]}: ${result.value.status}`); } if (result.status == "rejected") { alert(`${urls[num]}: ${result.reason}`); } }); }); 上の行 (*) の results は以下になります: [ {status: 'fulfilled', value: ...レスポンス...}, {status: 'fulfilled', value: ...レスポンス...}, {status: 'rejected', reason: ...エラーオブジェクト...} ] そのため、各 promise に対してステータスと 値 or エラー を取得します。
98
ブラウザはサポートしてない。
99
引数がプロミスのイテラブルを期待する関数を作る。 受け取ったプロミスをそれぞれ.thenでコールバックを登録する。 そのコールバック処理では、成功用、失敗用のオブジェクトを生成する ↑配列内のプロミスに対するコールバックの登録はMap関数を利用して行う。 .thenの戻り値はpromiseなので、それを配列として取得することになる。 生成されるオブジェクトはpromiseの結果に登録される。 上記処理をPromose.allに渡して返す。 if (!Promise.allSettled) { const rejectHandler = reason => ({ status: 'rejected', reason }); const resolveHandler = value => ({ status: 'fulfilled', value }); Promise.allSettled = function (promises) { const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler)); return Promise.all(convertedPromises); }; } Promise.resolve(p)の部分はpromiseじゃなくてプリミティブが配列の中に入ってきた時の対応 値がpromiseの場合は、そのまま返すので、この使い方で問題ない
100
これは Promise.all と同様ですが、すべてが完了するのを待つのではなく、最初の結果(またはエラー)のみを待ちます。 構文です: let promise = Promise.race(iterable); 例えば、ここでは結果は 1 になります: Promise.race([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1 なので、最初の結果/エラーが Promise.race 全体の結果になります。最初の確定した promise が “レースに勝った” 後、それ以外の結果/エラーは無視されます。