【JavaScript】Promiseの使い方 - 同期処理や非同期処理って何?
今回はJavaScriptで非同期処理に使うPromiseについて説明します。
説明する環境は以下です。
- macOS Catalina v10.15.5
- Visual Studio Code v1.57.0
Promiseはどんな時に使うの?
Promiseは外部からデータを受け取るAPI通信や重い処理がある場合に使います。
重い処理がある場合、その作業が完了するまで他の処理がスタートできません(同期処理)。そこで、重い処理はとりあえず横にズラしておいて、他の作業を先に進めておきます。重い作業が実行できるタイミングになったら実行します(非同期処理)。
同期処理と非同期処理のざっくりしたイメージは以下です。
同期処理は順番に処理が実行され、1つの処理が完了しないと次の処理が開始しません。
非同期処理は一旦作業を横にズラし、他の処理が完了してから実行します。横にズラしたあとも隙間時間を利用して処理を進め、いつでも実行できる状態で待機します。そして他の処理が終わったタイミングで実行してくれます。
そしてPromiseとはこの非同期処理を、簡単にかつ見やすく書けるようにしたものです。
Promiseの基本的な書き方
基本的な書き方は以下。Promiseの引数としてresolveとreject、2つのコールバック関数を指定します。rejectは必要なければ省略できます。
new Promise((resolve, reject) => {
}).then(
// 非同期処理が正常に終了したときに、ここが実行される
).catch(
// 非同期処理が失敗したときに、ここが実行される
).finally(
// 非同期処理が成功でも失敗でも、ここが実行される
);
Promiseは3つの状態を持っていて、それぞれの状態に合わせて処理を実行します。
pending:非同期処理が実行中の状態
fulfilled:非同期処理が正常に終了した状態
rejected:非同期処理が異常が起きて終了した状態
非同期処理が正常に終了するとPromiseの状態が「pending」から「fulfilled」に変わります。そのタイミングでresolveが実行され、thenの中の処理が実行されます。
rosolveが実行されるまで、thenの中身は実行されることはありません。特にエラーがなければcatchは無視されます。
new Promise((resolve, reject) => {
resolve("成功"); //非同期処理が正常に終了したときに呼ばれる関数(引数は1つのみ)
})
.then((value) => { //resolveの引数を受けとる
console.log(`処理が${value}しました`); //処理が成功しました と表示される
})
.catch()
.finally();
非同期処理に何かしらエラーが起こった場合はPromiseの状態が「pending」から「rejected」に変わります。そのタイミングでrejectが実行され、catchの中の処理が実行されます。
エラーがあった段階でその下にある処理は無視され、catchの中を実行します。
new Promise((resolve, reject) => {
reject("失敗"); //非同期処理が失敗したときに呼ばれる関数(引数は1つのみ)
})
.then()
.catch((value) => { //rejectの引数を受けとる
console.log(`処理が${value}しました`); //処理が失敗しました と表示される
})
.finally();
簡単にまとめると以下です。
- resolveは非同期処理が正常に終了したときに呼ばれる関数で、resolveが実行されるとthenの中身を実行!
- rejectは非同期処理に異常が起きて終了したときに呼ばれる関数で、rejectが実行されるとcatchの中身を実行!
実行される順番について
以下のようにthenをつないだ場合はどのような順番になるか見てみます。
new Promise((resolve, reject) => {
console.log('最初に実行');
resolve(1);
})
.then((num) => { //resolveの引数1を受けとる
console.log(`thenの${num}番目です`);
return num; //returnしないと以下のthenで引数が継承されず、numがundefinedになる
})
.then((num) => { //returnされたnumを受け取る
console.log(`thenの${num + 1}番目です`);
});
console.log('グローバル');
console.log('グローバルの2つ目');
//コンソールの表示
最初に実行
グローバル
グローバルの2つ目
thenの1番目です
thenの2番目です
thenは非同期処置なので、先にグローバル領域のタスクが実行され、それからthenの中が実行されます。
特定のthenの中でエラーを起こしてcatchメソッド実行
thenの中でエラーを起こすと、次のthenの処理が実行されず、catchメソッドが実行されます。
new Promise((resolve, reject) => {
console.log('最初に実行');
resolve(1);
})
.then((num) => {
console.log(`thenの${num}番目`);
throw new Error(); // エラーを起こすと、以下のthenは実行されず、catchメソッドが実行される
return num;
})
.then((num) => { //このthenは実行されない
console.log(`thenの${num + 1}番目`);
})
.catch(() => {
console.log('エラーをcatchしました');
});
console.log('グローバル');
//コンソールの表示
最初に実行
グローバル
thenの1番目です
エラーをcatchしました
catchの後にthenがある場合は、エラーが表示された後でも実行されます。
new Promise((resolve, reject) => {
console.log('最初に実行');
resolve(1);
})
.then((num) => {
console.log(`thenの${num}番目`);
throw new Error();
return num;
})
.then((num) => { //このthenは実行されない
console.log(`thenの${num + 1}番目`);
})
.catch(() => {
console.log('エラーをcatchしました');
})
.then(() => { //catchの後で実行される
console.log(`最後のthenです`);
});
console.log('グローバル');
//コンソールの表示
最初に実行
グローバル
thenの1番目です
エラーをcatchしました
最後のthenです
Promiseをチェーンでつなげる
const demo01 = (num) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num);
}, 1000);
});
};
const demo02 = (num) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num + 1);
}, 1000);
});
};
demo01(1)
.then(demo02)
.then((num) => {
console.log(num); //2が表示される
});
demo1とdemo2の関数では、returnでPromiseのインスタンスが返されます。Promiseのインスタンスが返されるということはthenが使えるということ。
16〜20行目のように、前の処理で得たデータを次の処理でも繋げて使うことができます。そしてthenの中はresolveが実行されないと実行されないので、thenで繋げた順番通り実行されていきます。
Promiseを使った並列実行
promiseの処理をthenでつなげていくと直列実行になりますが、並列実行をする場合は以下。
- Promise.all … 並んでいる非同期処理すべてが完了したら、次の処理を実行する
- Promise.race … 並んでいる非同期処理の1つでも完了したら、次の処理を実行する
Promise.all(すべてが完了したら、次を実行)
const demo1 = (val) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(val);
resolve(val);
}, 1000);
});
};
const demo2 = (val) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(val);
resolve(val);
}, 2000);
});
};
const demo3 = (val) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(val);
resolve(val);
}, 3000);
});
};
Promise.all([demo3(3), demo2(2), demo1(1)]).then(() => console.log("処理終了です"));
//console.log
1
2
3
処理終了です
Promise.allの中にある3つの関数が完了してから、最後のthenが実行されていることがわかります。
Promise.race(並列実行している1つでも完了したら、次を実行)
const demo1 = (val) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(val);
resolve(val);
}, 1000);
});
};
const demo2 = (val) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(val);
resolve(val);
}, 2000);
});
};
const demo3 = (val) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(val);
resolve(val);
}, 3000);
});
};
Promise.race([demo3(3), demo2(2), demo1(1)]).then(() => console.log("処理終了です"));
//console.log
1
処理終了です
2
3
Promise.raceで、1つでも関数が実行されればthenの中の実行します。
Promiseの具体的な例
Promiseを使って外部からデータを受けとって処理をする具体的な例は以下です。
const func = (url = "") => {
return new Promise((resolve, reject) => {
fetch(url)
.then((responce) => {
const date = responce.json();
return resolve(date);
})
.catch(() => {
return reject("失敗!");
});
});
};
func("https://yumegori.com/wp-json/wp/v2/posts?per_page=3&context=embed")
.then((value) => {
for (const elem of value) {
const dateInner = `
タイトル:${elem.title.rendered}
`;
console.log(dateInner);
}
})
.catch((err) => {
console.log(err);
});
WP REST APIを使って、このサイトの記事タイトルを3件取得しています。func関数で設定したURLをfetchで受けて、thenの引数のresponceにデータを渡しています。そのデータをJSON形式に変換して、そのデータをresolveに渡しています。
thenの引数のvalueには、resolveから渡された配列形式のデータが入っているので、ループを使って3件分の記事タイトルをコンソールに表示させています。
fetchについてはここでは詳しく説明しませんが、非同期でサーバー上にあるデータを取得することができるものです。戻り値にPromiseオブジェクトを返すので、thenやcatchが使えます。
fetchについては以下を参考にしてみてください。
まとめ
今回はJavaScriptのPromiseについて説明しました。非同期処理をする場合は、このPromiseの理解が不可欠です。fetchやasync/awaitの非同期処理でも戻り値はPromiseオブジェクトに設定されています。はじめて非同期処理をする場合は、まずPromiseから学んでいきましょう。
Promiseが理解できると、非同期処理ができるasyncとawaitも使えるようになります。asyncとawaitについては以下の記事を参考にしてみてください。
実際にasyncとawaitを使ってAPIからデータを取得する方法については以下が参考になります。