LogoMark.png

JavaScript/Asynchronous

JavaScript|Asynchronous Processing

JavaScript における非同期処理

CONTENTS


非同期処理とは

プログラミングには、同期処理(sync)と非同期処理(async)という大きな区別があります。

Promise

非同期処理の問題

非同期処理の問題は「コードの順番通りには実行されない」という点です。
例えば以下、

setTimeout(() => console.log('Hello'), 500);
console.log('world!');

このコードでは、setTimeoutのリクエストを終えた直後に2行目に移動して「world」が先に表示され、500ミリ秒が経過してから「Hello」が表示されます。処理が極端に遅く進行する状況であれば「Hello world!」となるかもしれませんが、通常は「world! Hello」となります。非同期処理では、実行順序がどうなるかわからない・・という問題があるわけです。
また、「xxxxが終わったらXXXXを実行せよ」というかたちで事前に処理内容を予約する仕組みは、これが連続する状況になるとコードが一気に複雑怪奇なものになってしまいます。

Promise の概要

そこで、非同期処理をわかりやすく記述できるように導入されたのが Promise という仕組みで、以下のような手続きを踏みます。

以上で「Promiseの実行後にXXXXする」という処理を書くことができます。

事例

const promise = new Promise( (resolve, reject) => {
  setTimeout( () => {
    console.log('Hello');
    resolve();
  }, 500 );
});
promise.then( () => console.log('world!') );

このコードの Promiseオブジェクトでは、500ミリ秒後に「hello」を表示した後 resolve関数で Promiseの終了を明示しています。Promise が resolve されると、thenメソッドに登録した関数が呼ばれて「world!」が表示されます。

resolve関数

Promiseを終了させる resolve関数には値を渡すこともできます。resolve関数に渡した値は thenメソッドで受け取ることができます。

const promise = new Promise( (resolve, reject) => {
   // Do Something
   resolve('Done!');
});
promise.then((r) => console.log(r));

Done!


reject関数

Promiseをエラー終了させる reject関数にも値を渡すことができます。

const promise = new Promise( (resolve, reject) => {
   // Do Something
   reject('Error!');
});
promise.then( () => console.log('Done!') )   // この場合は実行されない
             .catch( (e) => console.log(e) );  // こちらが実行される

Error!

reject関数に渡された値は catchメソッドで受け取ります(Promise が reject された場合は then に登録した関数は呼ばれません)。


thenチェーン

thenメソッドをチェーンする(thenの後に更にthenをつなげて書く)ことで、複数の非同期処理を直列に書くことができます。

非同期処理が必要な場合でも、実際には順序通りの動作が求められる場合があります。最も代表的な例は XHR(XMLHttpRequest)です。XHRを利用した外部ファイルの読み込みは基本的には非同期処理になりますが、例えば、sample01.txt, sample02.txt, sample03.txt を順番通りにひとつずつ読み込むことが必要になる場合、以下のように記載することで、順に実行することができるようになります。

function openFile(url) {
   const p = new Promise((resolve, reject) => {
     const xhr = new XMLHttpRequest();
     xhr.open('GET', url);
     xhr.addEventListener('load', (e) => resolve(xhr));
     xhr.send();
   });
   
   return p;
}

openFile('sample01.txt')
  .then((xhr) => openFile('sample02.txt'))
  .then((xhr) => openFile('sample03.txt'))
  .then((xhr) => console.log('Done!'));




async / await

await の意味

await は Promise を同期的に展開する(ように見せかける)機能です。Promiseの前に await を書くことで、Promiseの終了を待つことができます。例えば await を使わない Promise と then による記述では・・

const promise = new Promise( (resolve, reject) => {
    // Do Something.
    resolve('Hello world!'); 
});
let hw;
promise
  .then((r) => hw = r)
  .then(() => console.log(hw));

これに await を利用すると非同期処理を同期処理のように書くことができます。上記と同様の動作を await を用いて記述すると、以下のような記述になります(ただしこのままではエラー)。

const promise = new Promise( (resolve, reject) => {
    // Do Something.
    resolve('Hello world!'); 
});
const hw = await promise;
console.log(hw); // Hello world!


async

ただし await は「トップレベルでの使用は不可」となっていて「async がついた関数の中でしか利用できない」という条件があります。そこで、上の例を実際に動く形で書くと以下のようになります。

async function helloWorld() {
   const promise = new Promise( (resolve, reject) => {
       // Do Something.
       resolve('Hello world!'); 
   });
   const hw = await promise;
   console.log(hw);
}

helloWorld();

Hello world!


事例

前述の XHR(XMLHttpRequest)を用いて、sample01.txt, sample02.txt, sample03.txt を順番通りにひとつずつ読み込む事例を紹介します。

function openFile(url) {
   const p = new Promise( (resolve, reject) => {
     const xhr = new XMLHttpRequest();
     xhr.open('GET', url);
     xhr.addEventListener('load', (e) => resolve(xhr));
     xhr.send();
   });
   
   return p;
}
 
async function loadAllFiles() {
  const xhr1 = await openFile('sample01.txt');
  const xhr2 = await openFile('sample02.txt');
  const xhr3 = await openFile('sample03.txt');
  console.log('Done!');
}

loadAllFiles();






PAGES

GUIDE

TOOL

DATA

Last-modified: 2021-01-11 (月) 14:16:03