35
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-14

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

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

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

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

35
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?