最近、会社のフロントじゃない人にjavascriptを教えていて
「Promiseとはなんぞや?」という所から、
「async/awaitってのがあってね」という所まで解説しました。
非同期を制するものはjavascriptを制す、ということで、
その時の説明を記事にしておきます。
Promiseの使い方
まず、Promiseは次のような書き方をして使います。
new Promise((resolve) => {
resolve('Hello, World');
}).then((result) => {
console.log(result); // Hello, World
}).catch((error) => {
console.log(error);
});
- 最初の関数の中で、resolveを実行すると、次のthenの関数に処理が進みます。
- resolveに渡した引数が、次のthenの引数として渡ります
- 処理のどこかでerrorが起きた場合、catchの関数に処理が進みます
これがPromiseの基本的な動きです
Promiseの使いどころ
Promiseをどういうとき使うのかの話。
基本的には非同期な処理をするときに使います。
setTimeout(() => {
console.log(1);
}, 1000);
console.log(2);
これは非同期なので、2 → 1と表示されます。
しかし気持ちは 1 → 2の順で表示したい、という場合。
ここでPromiseを使うと次のように書けます
new Promise((resolve) => {
setTimeout(() => {
console.log(1);
resolve()
}, 1000);
}).then(() => {
console.log(2);
})
これで、1を表示したら、resolveして2を表示するように、
上から下へと順番に処理を読めるようになりました。
Promiseで並列処理
複数の非同期を扱いたい、というケースでもPromiseが役立ちます。
例えば次のように書いたとして、これは3つともほぼ同時に処理を開始します。
setTimeout(() => {
console.log(1);
}, 3000);
setTimeout(() => {
console.log(2);
}, 2000);
setTimeout(() => {
console.log(3);
}, 1000);
ここで「3つの処理が全て終わったとき、次の処理に進む」というコードを書きたいとします。
こういう場合 Promise.all が活躍します。
Promise.all([
new Promise(function(resolve) { setTimeout(resolve, 1000); }),
new Promise(function(resolve) { setTimeout(resolve, 1000); }),
new Promise(function(resolve) { setTimeout(resolve, 1000); }),
]).then((results) => {
console.log(results[0]); // 1つめのPromiseのresolve結果
console.log(results[1]); // 2つめのPromiseのresolve結果
console.log(results[2]); // 3つめのPromiseのresolve結果
})
Promise.allには、Promiseの配列を渡します。
全てのPromiseがresolveされると、次のthenに進みます。
次のthenの引数には、各Promiseのresolveで渡したものが、配列として渡されます
async/awaitの使い方
async/awaitは、Promseを別の方法で書けるようにしたものと思えばOKです。
例えば
new Promise(resolve => {
resolve('foo');
}).then(result => {
console.log(result); // foo
})
これが
const result = await new Promise(resolve => {
resolve('foo')
}) ;
console.log(result); // foo
というように書けるようになります(このコードだとエラーでまだ動きません)。
awaitの後ろには何かしら値を取ればよいので、よくPromiseを返す関数と組み合わせて使います。
function foo() {
return new Promise(resolve => {
resolve('foo')
})
}
const result = await foo();
console.log(result); // foo
そしてawait文を使うには、そのスコープがasyncのついた関数でなければなりません。つまり
const main = async () => {
function foo() {
return new Promise(resolve => {
resolve('foo')
})
}
const result = await foo();
console.log(result); // foo
}
main();
こんな感じでasyncのついた関数の中でawaitが使えるようになります
基本的に、awaitを付けた関数は、記述した順に実行されるので
const main = async () => {
function foo() {
return new Promise(resolve => {
resolve('foo')
})
}
function bar() {
return new Promise(resolve => {
resolve('bar')
})
}
const resultFoo = await foo();
const resultBar = await bar();
}
main();
と書いたらfooがresolveしたあとにbarを実行します。
こうしてPromiseを使った非同期処理が、普通の同期処理っぽく書けるようになりました。
async/awaitのエラー処理
Promiseで処理に失敗した場合はcatchを使っていましたが、
async/awaitではtry/catchが使えます。
try {
await foo()
}catch(e){
throw e;
}
このように、エラー処理も同期処理のように書けるようになります。
書いてみたはいいものの・・・
実際は業務で書いてるコードをもとに説明していたんですが、
実際のコードを抜くと、やっぱり説明しずらいかった。