• 最終更新日:

【JavaScript】Promiseの使い方 - 同期処理や非同期処理って何?

【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からデータを取得する方法については以下が参考になります。