Edited at

Promise と async/await を始めからていねいに

More than 1 year has passed since last update.

最近、会社のフロントじゃない人に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;
}

このように、エラー処理も同期処理のように書けるようになります。


書いてみたはいいものの・・・

実際は業務で書いてるコードをもとに説明していたんですが、

実際のコードを抜くと、やっぱり説明しずらいかった。