非Promiseな値をawaitする
Promise.resolve の使い所 に関連してもう一つ。 await
は Promise
じゃない値でも扱える。
上記記事の example より:
class {
private _prop: string
private get propPromise(): Promise<string> {
return Promise.resolve(
this._prop ||
API.fetch().then(remoteResource => {
this.prop = remoteResource.prop
return this.prop
})
)
}
doSomethingWithProp() {
const propValue = await this.propPromise
// using propValue ...
}
}
たとえば this._prop
の値をそのまま返すようにして、
戻り値の型は string | Promise<string>
にする:
private get propPromise(): string | Promise<string> {
return this._prop ||
API.fetch().then(remoteResource => {
this.prop = remoteResource.prop
return this.prop
})
}
doSomethingWithProp() {
const propValue = await this.propPromise
// using propValue ...
}
差分は以下:
- private get propPromise(): Promise<string> {
+ private get propPromise(): string | Promise<string> {
- return Promise.resolve(
- this._prop ||
+ return this._prop ||
API.fetch().then(remoteResource => {
this.prop = remoteResource.prop
return this.prop
})
- )
}
使う側が単に await
してる限りはこれでも動く。
呼び出した関数スコープで値を付加するために .then
したくなったら書き方が変わる。
createConvertedSomethingPromise(...args): Promise<Hoge> {
const converter = this.createConverter(...args)
return this.propPromise.then(prop => converter(prop))
}
こういうやつ。 <string>
返ったときに .then
生えてないから!ってぶっ壊れるので
Promise.resolve
で Promise
化する(Promise
が渡ってきたときはそのままになる):
createConvertedSomethingPromise(...args): Promise<Hoge> {
const converter = this.createConverter(...args)
- return this.propPromise.then(prop => converter(prop))
+ return Promise.resolve(this.propPromise).then(prop => converter(prop))
}
あるいは async
& await
- createConvertedSomethingPromise(...args): Promise<Hoge> {
+ async createConvertedSomething(...args): Promise<Hoge> {
const converter = this.createConverter(...args)
- return this.propPromise.then(prop => converter(prop))
+ const prop = await this.propPromise
+ return converter(prop)
}
戻り値の型が複数あると、呼び出し先でいちいちそれぞれの型に合わせたハンドリングする必要が出てくる。具体的に、この場合の Promise
化する処理のように、型を変換するコードが呼び出す毎に発生するので、
ワザワザやる必要はない。
話戻って await
の挙動について、
後続の値が Promise(というか thenable?)
のときは .then
のチェーンたどって unwrapして、 not Promise
だった場合はそのままスルーしてるのかと思っていたんだけど、どうやらそうじゃないらしい。
... If the value of the expression following the await operator is not a Promise, it's converted to a resolved Promise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#Description
要は await notPromiseValue
については await Promise.resolve(notPromiseValue)
に近い挙動を暗黙的に行っているようだ。(これが言いたかっただけ)
つまり、後続の値を Promise
化する。自分の想像と逆だった。
25.5.5.3 AsyncFunctionAwait ( value ) ... mean the same thing as:
- Let asyncContext be the running execution context.
- Let promiseCapability be ! NewPromiseCapability(%Promise%).
- Let resolveResult be ! Call(promiseCapability.[[Resolve]], undefined, « value »). ...
25.4.1.5 NewPromiseCapability ( C )
- If IsConstructor(C) is false, throw a TypeError exception.
- NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 25.4.3.1).
- Let promiseCapability be a new PromiseCapability { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }.
- Let executor be a new built-in function object as defined in GetCapabilitiesExecutor Functions.
- Set executor.[[Capability]] to promiseCapability.
- Let promise be ? Construct(C, « executor »).
- If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
- If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
- Set promiseCapability.[[Promise]] to promise.
- Return promiseCapability.
https://www.ecma-international.org/ecma-262/8.0/#sec-newpromisecapability
斜め読みした感じの解釈だけど、NewPromiseCapability
で Promise
作ったあとに
そこで (作ったPromiseCapability).resolve に await
する対象の value
を引数にして Call してる
ついでに Promise.resolve
はというと
25.4.4.5 Promise.resolve ( x )
- Let C be the this value.
- If Type(C) is not Object, throw a TypeError exception.
- If IsPromise(x) is true, then a. Let xConstructor be ? Get(x, "constructor"). b. If SameValue(xConstructor, C) is true, return x.
- Let promiseCapability be ? NewPromiseCapability(C).
- Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
- Return promiseCapability.[[Promise]].
https://www.ecma-international.org/ecma-262/8.0/#sec-promise.resolve
戻り値若干違ったりするけど、Promiseをresolveする手続きがほぼ一緒な感じ