Google Input Tools で使われる Promise の実装を読んでみた


~~ES6 で策定された Promise の挙動を Chrome で試してみていて、実装が気になったので chromium のソースコードを読んでみた。~~
(1/3 追記) コメントでツッコミもらったんですが、以下で読んだ `/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js` は Google Input Tools で使うための `Promise` 実装で、Chrome で動いているものとは別物でした。Chrome で動いてる `Promise` は、v8 の repository で実装されてるみたいです ( https://chromium.googlesource.com/v8/v8/+/master/src/js/promise.js )

対象となるファイルは、 choromium の `/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js` 

## Promise の生成

```
  76 goog.Promise = function(resolver, opt_context) {
  77   /**
  78    * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, or
  79    * BLOCKED.
  80    * @private {goog.Promise.State_}
  81    */
  82   this.state_ = goog.Promise.State_.PENDING;
.
.
.
 161   if (resolver != goog.nullFunction) {
 162     try {
 163       var self = this;
 164       resolver.call(
 165           opt_context,
 166           function(value) {
 167             self.resolve_(goog.Promise.State_.FULFILLED, value);
 168           },
 169           function(reason) {
.
.
.
 185             self.resolve_(goog.Promise.State_.REJECTED, reason);
 186           });
 187     } catch (e) {
 188       this.resolve_(goog.Promise.State_.REJECTED, e);
 189     }
 190   }
 191 };
```

**167行目** , **185行目**, **188行目** で `this.resolve_` を呼び出している。 `resolve_` の中で、真の resolve の処理を行う（Promise 生成段階ではまだ resolve の処理が登録されてないので、それを抽象化してる）。
state の管理をしていて、これは初期状態だと `goog.Promise.State_.PENDING` 、resolve か reject が確定すると `goog.Promise.State_.FULFILLED` か`goog.Promise.State_.REJECTED` になる。

```
 895 goog.Promise.prototype.resolve_ = function(state, x) {
 896   if (this.state_ != goog.Promise.State_.PENDING) {
 897     return;
 898   }
 899
 900   if (this == x) {
 901     state = goog.Promise.State_.REJECTED;
 902     x = new TypeError('Promise cannot resolve to itself');
 903   }
 904
 905   this.state_ = goog.Promise.State_.BLOCKED;
 906   var isThenable = goog.Promise.maybeThen_(
 907       x, this.unblockAndFulfill_, this.unblockAndReject_, this);
 908   if (isThenable) {
 909     return;
 910   }
 911
.
.
.
 923 };
```

906 行目の `maybeThen_` は、`value` が `Thenable` なオブジェクトの時の処理を行うっぽい。

```
 926 /**
 927  * Invokes the "then" method of an input value if that value is a Thenable. This
 928  * is a no-op if the value is not thenable.
 929  *
 930  * @param {*} value A potentially thenable value.
 931  * @param {!Function} onFulfilled
 932  * @param {!Function} onRejected
 933  * @param {*} context
 934  * @return {boolean} Whether the input value was thenable.
 935  * @private
 936  */
 937 goog.Promise.maybeThen_ = function(value, onFulfilled, onRejected, context) {
 938   if (value instanceof goog.Promise) {
 939     value.thenVoid(onFulfilled, onRejected, context);
 940     return true;
 941   } else if (goog.Thenable.isImplementedBy(value)) {
 942     value = /** @type {!goog.Thenable} */ (value);
 943     value.then(onFulfilled, onRejected, context);
 944     return true;
 945   } else if (goog.isObject(value)) {
 946     try {
 947       var then = value['then'];
 948       if (goog.isFunction(then)) {
 949         goog.Promise.tryThen_(
 950             value, then, onFulfilled, onRejected, context);
 951         return true;
 952       }
 953     } catch (e) {
 954       onRejected.call(context, e);
 955       return true;
 956     }
 957   }
 958
 959   return false;
 960 };
```

逆に、`Thenable` でない時は、 `this.resul_` に `x` を set してから、`this.state_` も `goog.Promise.State_.FULFILLED` もしくは `goog.Promise.State_.REJECTED` に更新。

また、 **916行目** で `this.parent_` への参照も切っている（これはGCのため？）
そして、 **917行目** の `this.scheduleCallbacks_()` でスケジューリング。

```
 895 goog.Promise.prototype.resolve_ = function(state, x) {
.
.
.
 912   this.result_ = x;
 913   this.state_ = state;
 914   // Since we can no longer be canceled, remove link to parent, so that the
 915   // child promise does not keep the parent promise alive.
 916   this.parent_ = null;
 917   this.scheduleCallbacks_();
 918
 919   if (state == goog.Promise.State_.REJECTED &&
 920       !(x instanceof goog.Promise.CancellationError)) {
 921     goog.Promise.addUnhandledRejection_(this, x);
 922   }
 933 }
```

`scheduleCallbacks_` の実装は以下。`this.executeCallbacks` を `sync` で実行する。

```
1022 goog.Promise.prototype.scheduleCallbacks_ = function() {
1023   if (!this.executing_) {
1024     this.executing_ = true;
1025     goog.async.run(this.executeCallbacks_, this);
1026   }
1027 };
```

`executeCallbacks_` の実装は以下。`popEntry` で登録された callback を取り出して、順番に実行する。おそらく、 `then` でチェーンした callback を順番に実行していくシチュエーション。

```
1101 goog.Promise.prototype.executeCallbacks_ = function() {
1102   var entry = null;
1103   while (entry = this.popEntry_()) {
1104     if (goog.Promise.LONG_STACK_TRACES) {
1105       this.currentStep_++;
1106     }
1107     this.executeCallback_(entry, this.state_, this.result_);
1108   }
1109   this.executing_ = false;
1110 };
```

`this.popEntry` では、chain list になった `this.callbackEntries` から `entry` を順番に取り出している。

```
1061 goog.Promise.prototype.popEntry_ = function() {
1062   var entry = null;
1063   if (this.callbackEntries_) {
1064     entry = this.callbackEntries_;
1065     this.callbackEntries_ = entry.next;
1066     entry.next = null;
1067   }
1068   // It the work queue is empty clear the tail too.
1069   if (!this.callbackEntries_) {
1070     this.callbackEntriesTail_ = null;
1071   }
1072
1073   if (entry != null) {
1074     goog.asserts.assert(entry.onFulfilled != null);
1075   }
1076   return entry;
1077 };
```

**1107行目** の `this.executeCallback_(entry, this.state_, this.result_)` で、entry の中身を実行（ entry は resolve と reject をどっちも持ってるので、state が必要）。ちなみにこの時点で、何も callback が登録されていない（ `this.callbackEntries` が初期値の null のままだ）と、null が返される。ので、promise を生成しただけの状態だと、 **1025行目** の `this.executeCallbacks_` は何もせずに終了するはず。実際、`Promise` を生成して `then` を呼び出さないと、何にも起きない。

逆に、`promise` に対して `then` を呼び出すたびにちゃんと callback は実行される。これはおそらく、`this.result_` で callback 用の値を残している為。つまり、`Promise` の初期化時に指定した処理はすぐに実行され、その時点で `this.value_` と `this.state_` は保存されて、その後は `then` を呼び出すたびに保存した値が使われる。

```
  76 goog.Promise = function(resolver, opt_context) {
.
.
.
  97   /**
  98    * The linked list of {@code onFulfilled} and {@code onRejected} callbacks
  99    * added to this Promise by calls to {@code then()}.
 100    * @private {?goog.Promise.CallbackEntry_}
 101    */
 102   this.callbackEntries_ = null;
 103
 104   /**
 105    * The tail of the linked list of {@code onFulfilled} and {@code onRejected}
 106    * callbacks added to this Promise by calls to {@code then()}.
 107    * @private {?goog.Promise.CallbackEntry_}
 108    */
 109   this.callbackEntriesTail_ = null;
```

おまけで `this.executeCallback_` の実装を見ておくと、以下の様になっている。

```
1124 goog.Promise.prototype.executeCallback_ = function(
1125     callbackEntry, state, result) {
1126   // Cancel an unhandled rejection if the then/thenVoid call had an onRejected.
1127   if (state == goog.Promise.State_.REJECTED &&
1128       callbackEntry.onRejected && !callbackEntry.always) {
1129     this.removeUnhandledRejection_();
1130   }
1131
1132   if (callbackEntry.child) {
1133     // When the parent is settled, the child no longer needs to hold on to it,
1134     // as the parent can no longer be canceled.
1135     callbackEntry.child.parent_ = null;
1136     goog.Promise.invokeCallback_(callbackEntry, state, result);
1137   } else {
1138     // Callbacks created with thenAlways or thenVoid do not have the rejection
1139     // handling code normally set up in the child Promise.
1140     try {
1141       callbackEntry.always ?
1142           callbackEntry.onFulfilled.call(callbackEntry.context) :
1143           goog.Promise.invokeCallback_(callbackEntry, state, result);
1144     } catch (err) {
1145       goog.Promise.handleRejection_.call(null, err);
1146     }
1147   }
1148   goog.Promise.returnEntry_(callbackEntry);
1149 };
```

## Promise.prototype.then の中身を見る
callback の登録のフローを見ていく。

```
 549 goog.Promise.prototype.then = function(
 550     opt_onFulfilled, opt_onRejected, opt_context) {
 551
 552   if (opt_onFulfilled != null) {
 553     goog.asserts.assertFunction(opt_onFulfilled,
 554         'opt_onFulfilled should be a function.');
 555   }
 556   if (opt_onRejected != null) {
 557     goog.asserts.assertFunction(opt_onRejected,
 558         'opt_onRejected should be a function. Did you pass opt_context ' +
 559         'as the second argument instead of the third?');
 560   }
 561
 562   if (goog.Promise.LONG_STACK_TRACES) {
 563     this.addStackTrace_(new Error('then'));
 564   }
 565
 566   return this.addChildPromise_(
 567       goog.isFunction(opt_onFulfilled) ? opt_onFulfilled : null,
 568       goog.isFunction(opt_onRejected) ? opt_onRejected : null,
 569       opt_context);
 570 };
 571 goog.Thenable.addImplementation(goog.Promise);
```

then の中ではほぼ何もしてなくて、ただ `addChildPromise_` を呼んでいる。

```
 812 goog.Promise.prototype.addChildPromise_ = function(
 813     onFulfilled, onRejected, opt_context) {
 814
 815   /** @type {goog.Promise.CallbackEntry_} */
 816   var callbackEntry = goog.Promise.getCallbackEntry_(null, null, null);
 817
 818   callbackEntry.child = new goog.Promise(function(resolve, reject) {
 819     // Invoke onFulfilled, or resolve with the parent's value if absent.
 820     callbackEntry.onFulfilled = onFulfilled ? function(value) {
 821       try {
 822         var result = onFulfilled.call(opt_context, value);
 823         resolve(result);
 824       } catch (err) {
 825         reject(err);
 826       }
 827     } : resolve;
 828
 829     // Invoke onRejected, or reject with the parent's reason if absent.
 830     callbackEntry.onRejected = onRejected ? function(reason) {
 831       try {
 832         var result = onRejected.call(opt_context, reason);
 833         if (!goog.isDef(result) &&
 834             reason instanceof goog.Promise.CancellationError) {
 835           // Propagate cancellation to children if no other result is returned.
 836           reject(reason);
 837         } else {
 838           resolve(result);
 839         }
 840       } catch (err) {
 841         reject(err);
 842       }
 843     } : reject;
 844   });
 845
 845
 846   callbackEntry.child.parent_ = this;
 847   this.addCallbackEntry_(callbackEntry);
 848   return callbackEntry.child;
 849 };
```
- 1. 空のcallbackEntryを作成
- 2. callbackEntry.child に、Promiseを設定。この際、この Promise で設定された `resolve` の 引数には、`onFullfilled` の返り値を設定する。
- 3. then を呼び出した promise を callbackEntry.child.parent_ として設定（`Promise` の親子関係を作る）。
- 4. this.addCallbackEntry_(callbackEntry) を呼ぶ（ここで callback の追加や実行を行う）
- 5. callbackEntry.child を返す（これが Promise になってる）

callbackEntryの初期化は `getCallbackEntry` で行っている。

```
 300 goog.Promise.getCallbackEntry_ = function(onFulfilled, onRejected, context) {
 301   var entry = goog.Promise.freelist_.get();
 302   entry.onFulfilled = onFulfilled;
 303   entry.onRejected = onRejected;
 304   entry.context = context;
 305   return entry;
 306 };
```

また、`addCallbackEntry_` は以下。ここで **787行目** で `schedleCallbacks_()` が呼ばれている為、`then` を呼ぶたびに callback の処理が実行される。

```
 783 goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) {
 784   if (!this.hasEntry_() &&
 785       (this.state_ == goog.Promise.State_.FULFILLED ||
 786        this.state_ == goog.Promise.State_.REJECTED)) {
 787     this.scheduleCallbacks_();
 788   }
 789   this.queueEntry_(callbackEntry);
 790 };
```

ちなみに、**784行目** の `hasEntry_` というのは `this.callbackEntries` の有無を確かめてるだけっぽい。

```
1034 goog.Promise.prototype.hasEntry_ = function() {
1035   return !!this.callbackEntries_;
1036 };
```

また、 **789行目** の `queueEntry_` で、chain list の`this.callbackEntries_` に対して callback の追加を行う。

```
1043 goog.Promise.prototype.queueEntry_ = function(entry) {
1044   goog.asserts.assert(entry.onFulfilled != null);
1045
1046   if (this.callbackEntriesTail_) {
1047     this.callbackEntriesTail_.next = entry;
1048     this.callbackEntriesTail_ = entry;
1049   } else {
1050     // It the work queue was empty set the head too.
1051     this.callbackEntries_ = entry;
1052     this.callbackEntriesTail_ = entry;
1053   }
1054 };
```

ここまでで、`callbackEntries_` への callback の追加処理を確認できた。

## まとめ
chromium では、`Promise` は以下の様な実装になっている。

- `new Promise()` に登録した処理は即座に実行される。この処理が実行された時点で、 `goog.Promise.State_.FULFILLED` か `goog.Promise.State_.FULFILLED` が `this.state_` として保存され、callback に渡される値である `this.result_` も保存される。この時点で、`then` で callback が登録されているか否かに関わらず `this.executeCallbacks_`（登録された callback が無くなるまで全て実行する処理）が走る。
- `then` の呼び出しで callback を登録する。この時、返り値として Promise を返すが、Promise の 初期化の際に 「callback の返り値を使う処理」を渡す。これによって chaining が実現できる。`then` の処理の際にも `this.executeCallbacks_` が走る。
- `then` に渡した callback の返り値が `Promise` のケースもあるので、それは `Promise` 生成時に走る `this.resolve_` の中で `this.maybeThen_` が走ることで処理される。