かつてコールバック地獄とか呼ばれていたJSの非同期処理は Promise
の登場で劇的に改善しました。
さらに async/await
の構文の登場によってこれらの非同期処理はさらに簡潔に書けるようになりました。
async/await
は何をしているのか ?
内部的には async/await
は Promise
にある then()
というメソッドを処理しています。
ここでTypeScriptの Promise
で定義されている then()
をみてみます。
interface Promise<T> {
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
}
Promise
には catch(), finally()
といった他のメソッドもあるのですが async/await
がみているのは then()
だけです。
async/await
に対応したクラスをつくりたい !!
then()
を Promise
と同じシグネチャで実装すれば async/await
に対応させることができます。
またTypeScriptにはそのための限定的なインターフェイスとして PromiseLike
があります、これを実装すればお手製の async/await
に対応したクラスを作ることができます。
(TypeScriptの仕組みで、実際に PromiseLike
を実装しなくても、同じメソッドシグネチャがあれば実は対応できます)
interface PromiseLike<T> {
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
}
Promise
を実装すると catch(), finally()
など他のメソッドも実装する必要がありますが、これなら簡単です。
実食
class Gotta implements PromiseLike<string> {
public ingredients: Array<string>;
public constructor(...ingredients: Array<string>) {
this.ingredients = ingredients;
}
public get(): string {
return this.ingredients.join(', ');
}
public then<TResult1 = string, TResult2 = never>(
onfulfilled?: ((value: string) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): PromiseLike<TResult1 | TResult2> {
return new Promise<string>((resolve: (ret: string) => void) => {
resolve('PANCAKE!!!');
}).then(onfulfilled, onrejected);
}
}
それっぽいクラスを作りました。これを以下のように呼び出します。
const gotta: Gotta = new Gotta('flour', 'baking powder', 'egg', 'sugar', 'salt', 'milk', 'vegetable oil');
console.log(gotta.get());
// 'flour, baking powder, egg, sugar, salt, milk, vegetable oil'
console.log(await gotta);
// 'PANCAKE!!!'
やり方次第でもうなんでも await
できるようになります。
then()
実装のコツ
then()
が要求する引数は null, undefined
可で非常に作りづらいので、一度 Promise
のインスタンスを作成して、その then()
に引数を任せるのが簡単です。
注意
then()
を独自実装したクラスのインスタンスは Promise
で包んでしまうと中身が飛び出ます。
console.log(await Promise.resolve(gotta));
// 'PANCAKE!!!'
こんなことするわけないやろ。と思われるかもしれませんが async
をつけた非同期の関数でこの問題が起こりえます。
const cooking = async (): Promise<Gotta> => {
return new Gotta('flour', 'baking powder', 'egg', 'sugar', 'salt', 'milk', 'vegetable oil');
}
console.log(await cooking());
// 'PANCAKE!!!'
async
の関数内で使うときは独自色を出しすぎた then()
を持つクラスのインスタンスの取り扱いに気をつけてください。