JavaScript
promise

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

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

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

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

実際は業務で書いてるコードをもとに説明していたんですが、
実際のコードを抜くと、やっぱり説明しずらいかった。