データ型1 | データ型2 | ... | データ型n
type 名前 = データ型;
リスト : Type Alias の使用例 (sample16.ts)
// 以下のプログラムは --strictNullChecks を指定するとコンパイルエラーになる
// Deno の場合、デフォルトで strictNullChecks は true に設定されている
// 簡単な連結リスト
type LinkedList<T> = { item: T; next: LinkedList<T> };
const ls0: LinkedList<number> = { item: 0, next: null };
const ls1 = { item: 1, next: ls0 };
const ls2 = { item: 2, next: ls1 };
console.log(ls2.item);
console.log(ls2.next.item);
console.log(ls2.next.next.item);
console.log(ls2.next.next.next);
console.log(ls2);
$ deno check sample16.ts
Check sample16.ts
TS2322 [ERROR]: Type 'null' is not assignable to type 'LinkedList<number>'.
const ls0: LinkedList<number> = { item: 0, next: null };
~~~~
at file:///home/mhiro/js/sample16.ts:6:44
The expected type comes from property 'next' which is declared here on type 'LinkedList<number>'
type LinkedList<T> = { item: T; next: LinkedList<T> };
~~~~
at file:///home/mhiro/js/sample16.ts:4:33
error: Type checking failed.
リスト : Union Types の使用例 (sample17.ts)
// 以下のプログラムは --strictNullChecks を指定してもコンパイルできる
// 空リスト (本来ならばシングルトンにする)
class Nil {
get first(): never { throw new Error("empty list"); };
get rest(): never { throw new Error("empty list"); }
}
// コンスセル
class Cons<T> {
constructor(private _first: T, private _rest: List<T>) {}
get first(): T { return this._first; }
get rest(): List<T> { return this._rest; }
}
// 連結リスト
type List<T> = Nil | Cons<T>;
// 終端
const nil = new Nil();
// 生成関数
function cons<T>(x: T, xs: List<T>): List<T> {
return new Cons<T>(x, xs);
}
const xs0 = cons<number>(0, nil);
const xs1 = cons(1, xs0);
const xs2 = cons(2, xs1);
console.log(xs2.first);
console.log(xs2.rest.first);
console.log(xs2.rest.rest.first);
console.log(xs2);
$ deno check sample17.ts
Check sample17.ts
$ deno run sample17.ts
2
1
0
Cons {
_first: 2,
_rest: Cons { _first: 1, _rest: Cons { _first: 0, _rest: Nil {} } }
}
リスト : 抽象クラスを使った連結リストの実装 (sample18.ts)
abstract class List<T> {
abstract get first(): T;
abstract get rest(): List<T>;
}
// 空リスト (本来ならばシングルトンにする)
class Nil extends List<never> {
get first(): never { throw new Error("empty list"); };
get rest(): never { throw new Error("empty list"); }
}
// コンスセル
class Cons<T> extends List<T> {
constructor(private _first: T, private _rest: List<T>) { super(); }
get first(): T { return this._first; }
get rest(): List<T> { return this._rest; }
}
// 終端
const nil = new Nil();
// 生成関数
function cons<T>(x: T, xs: List<T>): List<T> {
return new Cons<T>(x, xs);
}
const xs0 = cons<number>(0, nil);
const xs1 = cons(1, xs0);
const xs2 = cons(2, xs1);
console.log(xs2.first);
console.log(xs2.rest.first);
console.log(xs2.rest.rest.first);
console.log(xs2);
$ deno check sample18.ts
Check sample18.ts
mhiro@mhiroi:~/js$ deno run sample18.ts
2
1
0
Cons {
_first: 2,
_rest: Cons { _first: 1, _rest: Cons { _first: 0, _rest: Nil {} } }
}
リスト : never 型 (sample19.ts)
class Foo { foo: number = 1; }
class Bar extends Foo { bar: number = 2 }
class Baz extends Bar { baz: number = 3 }
function foo<T extends Bar>(): void { console.log("oops"); }
// foo<Foo>(); コンパイルエラー
foo<Bar>();
foo<Baz>();
// foo<number>(); コンパイルエラー
foo<any>();
foo<never>();
$ deno check sample19.ts Check sample19.ts mhiro@mhiroi:~/js$ deno run sample19.ts oops oops oops oops
<データ型名> 値 値 as データ型名
リスト : Type Assertions の使用例 (sample20.ts)
class FooX {
constructor(private _x: number) { }
get x(): number { return this._x; }
}
class BarX extends FooX {
constructor(x: number, private _y: number) {
super(x);
}
get y(): number { return this._y; }
}
class BazX extends FooX {
constructor(x: number, private _z: string) {
super(x);
}
get z(): string { return this._z; }
}
// アップキャスト
const objFoo0: FooX = new BarX(123, 456);
const objFoo1: FooX = new BazX(789, "hello, world");
// console.log(objFoo0.y); コンパイルエラー
// console.log(objFoo1.z); コンパイルエラー
// ダウンキャスト (正常に実行)
console.log((<BarX>objFoo0).y); // 456
console.log((<BazX>objFoo1).z); // hello, world
// コンパイルできるが実行結果は undefined
// objFoo0 は BazX のオブジェクトではないから
console.log((<BazX>objFoo0).z); // undefined
$ deno check sample20.ts Check sample20.ts $ deno run sample20.ts 456 hello, world undefined
function 関数名(仮引数: データ型): 仮引数 is データ型 { ... }
リスト : Type Guards の使用例 (sample21.ts)
function isFoo(x: {foo: string} | undefined): x is {foo: string} {
if (!x) return false;
return typeof x.foo === 'string';
}
function typeSample(obj: number | string | Array<number> | {foo: string} | undefined): void {
if (typeof obj === 'number') {
// obj は数値
console.log(obj + 123);
} else if (typeof obj === 'string') {
// obj は文字列
console.log("hello, " + obj);
} else if (obj instanceof Array) {
// obj は配列
console.log(obj.reduce((a, x) => a + x));
} else if (isFoo(obj)) {
// obj は {foo: string} と互換性あり
console.log(`foo: ${obj.foo}`);
} else {
// 残りは undefined
console.log("oops!!");
}
}
typeSample(1000);
typeSample("bar");
typeSample([1,2,3,4,5]);
typeSample({foo: 'oops!!'});
typeSample(undefined);
$ deno check sample21.ts Check sample21.ts $ deno run sample21.ts 1123 hello, bar 15 foo: oops!! oops!!
リスト : Intersection Types の使用例 (sample22.ts)
type FooI = { foo: number };
type BarI = { bar: string };
type BazI = FooI & BarI;
const objX: FooI = { foo: 123 };
const objY: BarI = { bar: "hello, world" };
let objZ: BazI;
// objZ = objX; コンパイルエラー
// objZ = objY; コンパイルエラー
objZ = { foo: 456, bar: "oops!!" }; // OK
console.log(objX);
console.log(objY);
console.log(objZ);
$ deno check sample22.ts
Check sample22.ts
mhiro@mhiroi:~/js$ deno run sample22.ts
{ foo: 123 }
{ bar: "hello, world" }
{ foo: 456, bar: "oops!!" }
type Fruit = "Apple" | "Grape" | "Orange";
リスト : String Literal Types の使用例 (sample23.ts)
// 果物
type Fruit = "Apple" | "Grape" | "Orange" | "Banana";
// 果物の価格
class FruitPrice {
constructor(private _kind: Fruit, private _price: number) { }
get kind(): Fruit { return this._kind; }
get price(): number { return this._price; }
}
// 価格表
const priceTable: FruitPrice[] = [
new FruitPrice("Apple", 100),
new FruitPrice("Grape", 150),
new FruitPrice("Orange", 80)
];
// 価格を求める
function getPrice(x: Fruit, xs: FruitPrice[]): number {
for (let fruit of xs) {
if (fruit.kind == x) return fruit.price;
}
return 0;
}
console.log(getPrice("Apple", priceTable));
console.log(getPrice("Grape", priceTable));
console.log(getPrice("Orange", priceTable));
console.log(getPrice("Banana", priceTable));
// 簡単な Option 型
type Option<T> = "None" | { some: T };
function isNone<T>(x: Option<T>): x is "None" { return typeof x === 'string'; }
function isSome<T>(x: Option<T>): x is {some: T} { return typeof x !== 'string'; }
// Option から値を求める
function getValue<T>(x: Option<T>, y?: T) {
if (isSome<T>(x)) {
return x.some;
} else if (y !== undefined) {
return y;
}
throw new Error("getValue: value is none");
}
// 探索
function findIf<T>(pred: (x: T) => boolean, xs: T[]): Option<T> {
for (let y of xs) {
if (pred(y)) return { some: y };
}
return "None";
}
let result = findIf(x => x.kind == 'Apple', priceTable);
if (isSome(result))
console.log(getValue(result).price);
else
console.log("sold out");
result = findIf(x => x.kind == 'Banana', priceTable);
if (isSome(result))
console.log(getValue(result).price);
else
console.log("sold out");
$ deno check sample23.ts Check sample23.ts $ deno run sample23.ts 100 150 80 0 100 sold out
リスト : 関数の多重定義 (sample24.ts)
// 関数宣言
function foo(): void;
function foo(x: number): void;
function foo(x: string): void;
// 実装
function foo(x?: any): void {
if (x === undefined) {
console.log("hello, foo");
} else if (typeof x === 'number') {
while (x-- > 0) console.log("hello, foo!");
} else {
console.log(`hello, ${x}!!`);
}
}
foo();
foo(5);
foo("oops");
$ deno check sample24.ts Check sample24.ts $ deno run sample24.ts hello, foo hello, foo! hello, foo! hello, foo! hello, foo! hello, foo! hello, oops!!
リスト : モジュールの例 (module_foo.ts)
let foo: number = 1234;
const bar: number = 1.2345;
function baz(): string { return "baz"; }
export { foo, bar, baz }
リスト : import の使用例 (foo.ts)
import { foo, bar, baz } from "./module_foo.ts";
console.log(foo);
console.log(bar);
console.log(baz());
$ deno check module_foo.ts Check module_foo.ts $ deno check foo.ts Check foo.ts $ deno run foo.ts 1234 1.2345 baz
try {
処理A;
}
catch (引数: unknown) {
処理B;
}
finally {
処理C;
}
図 : 例外処理
リスト : try - catch - finally の使用例 (test_try.ts)
try {
// エラーの送出
throw new Error("oops!");
} catch (error: unknown) {
// エラーの捕捉(strict mode ではデフォルトで unknown 型)
if (error instanceof Error) {
console.error("Error message:", error.message);
} else {
console.error("Unexpected error:", error);
}
} finally {
// エラーの有無に関わらず必ず実行される処理
console.log("clean up");
}
$ deno check test_try.ts Check test_try.ts $ deno run test_try.ts Error message: oops! clean up
リスト : 例外の定義 (test_error.ts)
// Error を継承する
class FooError extends Error {}
try {
// エラーの送出
throw new FooError("oops!");
} catch (error: unknown) {
// エラーの捕捉
if (error instanceof FooError) {
console.error("Error message:", error.message);
} else {
console.error("Unexpected error:", error);
}
}
$ deno check test_error.ts Check test_error.ts $ deno run test_error.ts Error message: oops!
using宣言は、ファイルハンドルやデータベース接続など、解放が必要なリソースをスコープを抜けたときに自動でクリーンアップするための機能です。TypeScript 5.2 から導入されました。従来の try-finally ブロックによる手動の解放処理を置き換えることで、コードの記述漏れや可読性の低下を防ぐことができます。
using 変数名 = オブジェクト
using で宣言されたオブジェクトは、Disposable インターフェースを実装している必要があります。
リスト : Disposable インターフェース
interface Disposable {
[Symbol.dispose](): void;
}
using 宣言したオブジェクトがスコープを抜けるとき、メソッド [Symbol.dispose] が自動的に実行されます。
簡単な実行例を示します。
リスト : using 宣言の使用例 (test_using.ts)
class MyResource implements Disposable {
constructor() {
console.log("リソースを取得しました");
}
[Symbol.dispose]() {
console.log("リソースが自動解放されました!");
}
doSomething() {
console.log("処理を実行中...");
}
}
function process() {
using res = new MyResource();
res.doSomething();
console.log("処理が終了しました");
}
// 実行
process();
$ deno check test_using.ts Check test_using.ts $ deno run test_using.ts リソースを取得しました 処理を実行中... 処理が終了しました リソースが自動解放されました!
関数 process が終了したとき、using で宣言した変数 res がスコープを抜けるので、"処理が終了しました" と表示された後、"リソースが自動解放されました!" が表示されます。もし、{ using res = ...; res.doSomething(); } とブロックで囲むと、res のスコープはこのブロックの中なので、"リソースが自動解放されました!" が表示されてから、"処理が終了しました" と表示されます。興味のある方は試してみてください。
using 宣言は、例外が送出された場合でも確実にリソースを解放するように設計されています。using ブロックの途中で例外が送出された場合でも、呼び出し元にエラーが伝播する前に必ずメソッド [Symbol.dispose] が実行されます。
簡単な実行例を示します。
リスト : using 宣言と例外処理 (test_using1.ts)
class MyResource implements Disposable {
constructor() {
console.log("リソースを取得しました");
}
[Symbol.dispose]() {
console.log("リソースが自動解放されました!");
}
doSomething() {
console.log("処理を実行中...");
throw new Error("oops!!");
}
}
function process() {
using res = new MyResource();
res.doSomething();
console.log("処理が終了しました");
}
// 実行
try {
process();
} catch (error: unknown) {
if (error instanceof Error) {
console.error("Error message:", error.message);
} else {
console.error("Unexpected error:", error);
}
}
$ deno check test_using1.ts Check test_using1.ts $ deno run test_using1.ts リソースを取得しました 処理を実行中... リソースが自動解放されました! Error message: oops!!
try - catch で例外が捕捉される前に、メソッド [Symbol.dispose] が実行されていることがわかります。
TypeScript / JavaScript の非同期処理は、時間のかかる処理 (通信やファイルの読み込みなど) の完了を待たずに、次の処理を進めるための仕組みです。JavaScript はシングルスレッドで動作するため、時間のかかる処理で画面やプログラム全体が固まる (ブロックする) のを防ぐために使用されます。なお、TypeScript の非同期処理は基本的に JavaScript と同じです。詳細は拙作のページ JavaScript 超入門: プロミスとプロキシ をお読みくださいませ。
最近の TypeScript では、主に Promise<T> 型 と async / await 構文 を使って非同期処理をプログラムします。非同期処理の「未来に返ってくる結果」を表すオブジェクトが Promise です。TypeScriptでは、ジェネリクスを使って Promise<返ってくる値の型> と定義します。
new Promise<T>((resolve, reject) => { ... }) => promise_object (pending 状態)
Promise() には関数を渡します。(resolve, reject) => { ... } と書けば、TypeScript が自動で resolve と reject の型を推論してくれます。
Promise() はまだ計算を完了していない promise_object を返します。これを pending 状態といいます。引数 resolve, reject は関数で、処理 { ... } の中で resolve() を呼ぶと計算が完了したことになります。これを fulfilled 状態といいます。このとき、resolve() の引数に計算結果を渡します。reject() を呼ぶと計算が失敗したことになります。これを rejected 状態といいます。reject() の引数にはエラーの情報を渡します。
Promise() を実行すると、引数の関数はすぐに実行されることに注意してください。実際に非同期処理を行うには、function の中で非同期処理を行う関数、たとえば setTimeout() などを実行する必要があります。それに渡すコールバック関数の中で resolve() や reject() を呼び出します。
簡単な例を示しましょう。
リスト : 非同期処理の実行例 (async.ts)
// d msec 後に関数 func を実行する非同期関数
function makePromise(func: () => void, d: number): Promise<void> {
return new Promise((resolve, reject) => {
setTimeout(() => { func(); resolve(); }, d);
});
}
// コールバック関数
function callback_a(): void {
console.log("callback_a");
}
function callback_b(): void {
console.log("callback_b");
}
function callback_c(): void {
console.log("callback_c");
}
// 実行
function test0(): void {
makePromise(callback_a, 3000);
makePromise(callback_b, 1000);
makePromise(callback_c, 2000);
}
$ deno repl --eval-file=async.ts Deno 2.9.7 exit using ctrl+d, ctrl+c, or close() > test0() undefined > callback_b callback_c callback_a
関数 makePromise() は d msec 後に引数 func を実行する Promise を生成して返します。関数 test0() では、関数 callback_a, b, c を makePromise に渡して Promise を生成します。そして、指定した時間 (d msec) 後に渡したコールバック関数が実行されます。この場合、関数は非同期で実行されるので、待ち時間が短い順で (callback_b, c, a) コールバック関数が実行されます。
このように、コールバック関数の起動タイミングは Promise で異なるため、上から下へ順番に進む同期処理のようにコールバック関数を実行することはできません。Promise を同期処理のように書くための構文が async / await です。
async が付いた関数を async 関数と呼ぶことにします。await は Promise が結果を返すまで、async 関数の実行を一時停止します。プロミスの実行が終了すると、await はその結果を取り出して返します。そのあと、async 関数の実行が再開されます。簡単な例を示しましょう。
リスト : async / await の使用例
async function test1(): Promise<void> {
await makePromise(callback_a, 3000);
await makePromise(callback_b, 1000);
await makePromise(callback_c, 2000);
}
$ deno repl --eval-file=async.ts
Deno 2.9.7
exit using ctrl+d, ctrl+c, or close()
> test1()
Promise { <pending> }
> callback_a
callback_b
callback_c
関数 test1() は async がついているので Promise を返します。makePromise() の前に await が付いているので、Promise が値を返すまで test1() の実行は中断されます。したがって、コードの順番でコールバック関数 (callback_a, b, c) が実行されます。
Promise で計算が失敗 (reject) した場合、Promise のメソッド catch() でその値を求めることができました。async / await を使用すると、reject した時点で throw() と同様に例外が送出され、その例外は try - catch で補足することができます。
簡単な例を示しましょう。次のリストを見てください。
リスト : async / await の例外処理 (async1.ts)
// d msec 後に関数 func を実行する非同期関数
function makePromise(func: () => void, d: number): Promise<void> {
return new Promise((resolve, reject) => {
setTimeout(() => { func(); resolve(); }, d);
});
}
// コールバック関数
function callback_a(): void {
console.log("callback_a");
}
function callback_b(): void {
console.log("callback_b");
}
function callback_c(): void {
console.log("callback_c");
}
// エラーを送出する Promise を返す
function makePromiseReject(func: () => void, d: number): Promise<void> {
return new Promise((resolve, reject) => {
setTimeout(() => { func(); reject(new Error("oops!")); }, d);
});
}
async function test2(): Promise<void> {
await makePromise(callback_a, 3000);
await makePromiseReject(callback_b, 1000);
await makePromise(callback_c, 2000);
}
try {
await test2();
} catch (error: unknown) {
if (error instanceof Error) {
console.log("Error message:", error.message);
} else {
console.log("Unexpected error", error);
}
}
$ deno check async1.ts Check async1.ts $ deno run async1.ts callback_a callback_b Error message: oops!
関数 makePromiseReject() は関数 func を実行したあと、reject() を実行してエラーを送出します。test2() は callback_a, c を makePromise() で実行し、callback_b は makePromiseReject() で実行します。当然ですが、callback_b を実行したあとエラーが送出されるので、callback_c は実行されません。このエラーを try - catch で捕捉することができます。ここで、try 節の中で test2() を実行するとき、await() を付けることを忘れないでください。await を付け忘れると、送出されたエラーを捕捉することができません。ご注意くださいませ。
Promise.all は、複数の非同期処理 (Promise) を並行して実行し、それらすべてが完了するのを待つための静的メソッドです。TypeScript では、渡した配列やタプルの型に応じて自動的に厳格な型推論が行われます。
Promise.all([ps1, ps2, ...]) => [r1, r2, ...]
Promise.all に Promise の配列を渡すと、それぞれの結果が渡した順序のままタプル型 [r1, r2, ...] として返されます。
簡単な例を示しましょう。
リスト : Promise.all の使用例 (async2.ts)
function callback_a(): string {
console.log("callback_a");
return "foo";
}
function callback_b(): string {
console.log("callback_b");
return "bar";
}
function callback_c(): string {
console.log("callback_c");
return "baz";
}
function makePromise(func: () => string, d: number): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => { let r = func(); resolve(r); }, d);
});
}
async function test(): Promise<void> {
let [a, b, c] = await Promise.all([
makePromise(callback_a, 3000),
makePromise(callback_b, 1000),
makePromise(callback_c, 2000)
]);
console.log(a);
console.log(b);
console.log(c);
}
test();
$ deno check async2.ts Check async2.ts $ deno run async2.ts callback_b callback_c callback_a foo bar baz
コールバック関数 callback_a, b, c は文字列を返すように修正します。makePromise では func() の返り値を変数 r に受け取り、それを resolver() に渡します。makePromise の返り値の型は Promise<string> になります。あとは関数 test で、Promise.all を呼び出して、すべての Promise が終了するのを待ちます。最後に、Promise から受け取った文字列を console.log で表示します。
Promise.all は「すべて成功すること」を前提としています。渡された Promise のうち 1 つでも拒否 (Reject / エラー) されると、他の処理が進行中であっても、その時点で全体が即座にエラーとなります。ただし、バックグランドで進行している処理は停止せず、最後まで実行されることに注意してください。
簡単な例を示しましょう。
リスト : Promise.all の例外処理 (async3.ts)
function callback_a(): string {
console.log("callback_a");
return "foo";
}
function callback_b(): string {
console.log("callback_b");
return "bar";
}
function callback_c(): string {
console.log("callback_c");
return "baz";
}
function makePromise(func: () => string, d: number): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => { let r = func(); resolve(r); }, d);
});
}
function makePromiseReject(func: () => string, d: number): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => { let r = func(); reject(new Error(r)); }, d);
});
}
async function test(): Promise<void> {
let [a, b, c] = await Promise.all([
makePromise(callback_a, 3000),
makePromiseReject(callback_b, 1000),
makePromise(callback_c, 2000)
]);
console.log(a);
console.log(b);
console.log(c);
}
try {
await test();
console.log("all finish!"); // ここには到達しない
} catch (error: unknown) {
if (error instanceof Error) {
console.log("Error message:", error.message);
} else {
console.log("Unexpected error", error);
}
}
$ deno check async3.ts Check async3.ts $ deno run async3.ts callback_b Error message: bar callback_c callback_a
関数 makePromiseReject は関数 func を実行したあと、reject() でエラーを送出します。callback_b は makePromiseReject で生成した Promise で実行されるので、callback_b を表示したあとエラーが送出されます。それを try - catch で捕捉するので、try 節で test() のあとの処理は実行されません。ですが、callback_a, c の Promise はバックグランドで実行されているので、エラーメッセージのあとに callback_a, callback_c が表示されます。
ES2020 で導入された Promise.allSettled は、渡されたすべての非同期処理 (Promise) が「成功(Fulfilled)または失敗(Rejected)のどちらかで決着がつく (Settled)」まで、すべてを並列で実行して待つ静的メソッドです。Promise.all のように、途中で 1 つがエラーになったからといって全体を中断することはありません。すべての処理が最後まで走りきります。
簡単な例を示しましょう。
リスト : Promise.allSettled の使用例 (async4.ts)
function callback_a(): string {
console.log("callback_a");
return "foo";
}
function callback_b(): string {
console.log("callback_b");
return "bar";
}
function callback_c(): string {
console.log("callback_c");
return "baz";
}
function makePromise(func: () => string, d: number): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => { let r = func(); resolve(r); }, d);
});
}
function makePromiseReject(func: () => string, d: number): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => { let r = func(); reject(new Error(r)); }, d);
});
}
async function test(): Promise<void> {
let results = await Promise.allSettled([
makePromise(callback_a, 3000),
makePromiseReject(callback_b, 1000),
makePromise(callback_c, 2000)
]);
console.log(results);
}
try {
await test();
console.log("all finish!");
} catch (error: unknown) {
// エラーは捕捉されない
if (error instanceof Error) {
console.log("Error message:", error.message);
} else {
console.log("Unexpected error", error);
}
}
$ deno check async4.ts
Check async4.ts
$ deno run async4.ts
callback_b
callback_c
callback_a
[
{ status: "fulfilled", value: "foo" },
{
status: "rejected",
reason: Error: bar
... 略 ...
},
{ status: "fulfilled", value: "baz" }
]
all finish!
callback_b の Promise は reject されますが、そのエラーは catch 節に捕捉されません。すべての Promise が最後まで実行され、その結果が配列に格納されて返されます。
このほかにも、Promise には Promise.any や Promise.race など便利なメソッドがいろいろ用意されています。詳細はマニュアル JavaScript - Promise | MDN をお読みくださいませ。