JavaScript / TypeScript 非同期処理の覚書

Pocket

最近 TypeScript に触れる機会が少し増えています。非同期処理についていつも思い出すのに時間がかかるので改めて覚書としてまとめます。

TypeScript は「プロを目指す人のための TuypeScript 入門」 鈴木僚太著 を呼んでいます。

非同期処理

TypeScript の非同期処理についての覚書です。

本ページで引用元を示していない場合は上記書籍から引用しています。

非同期処理の実行

同期処理が完了した後、マイクロタスク(Promise のコールバックなど)、続いてマクロタスク(setTimeout など)がイベントループによって順に処理されます。

非同期処理の分類(マイクロタスク, マクロタスク)

JavaScript の非同期処理は、イベントループを基盤にマイクロタスクとマクロタスクに分類されます。

分類 代表的な例 実行順序の優先度
マイクロタスク Promise.then, Promise.catch, Promise.finally など コールスタック(同期処理のスタック)が空の直後に実行
マクロタスク setTimeout, setInterval, I/O など マイクロタスクがすべて完了した後に実行

実行順序の優先度の詳細(イベントループの実行順序)

  1. コールスタックが空になる
  2. マイクロタスクをすべて処理
  3. マクロタスクを 1 つ取り出して実行
  4. 1 に戻る

サンプル

1. 概要

function sampleAsynchronous() {
  return new Promise<string>(resolve => {
      console.log('First.');        // ----- (1)
      resolve('Fourth.')
  }
)};

// new Promise の中は同期的に実行されます
const promiseObj = sampleAsynchronous();

setTimeout(() => { 
    console.log('Fifth.')           // ----- (5)
},0)

console.log('Second.');             // ----- (2)


promiseObj.then((data: string) => {
  console.log(data)                 // ----- (4)
});

console.log('Third.')               // ----- (3)


// First.
// Second.
// Third.
// Fourth.
// Fifth.
実行順 タスク内容 種類 解説
(1) console.log('First.') 同期処理 sampleAsynchronous 関数内の Promise コンストラクタの中
(2) console.log('Second.') 同期処理 メインスレッドの通常の処理
(3) console.log('Third.') 同期処理 同上
(4) console.log(data)Fourth. マイクロタスク Promise.resolve(...).then(...) のコールバック
(5) console.log('Fifth.') マクロタスク setTimeout(..., 0) によりキューに登録されたコールバック

2. 詳細

// (N) は N 番目に実行

function createPromise() {
  return new Promise<string>(resolve => {
      console.log('task 2');        // (2) -> 同期処理
      resolve('task 4')
  }
)};

function createOtherPromise() {
  return new Promise<string>(resolve => {
      console.log('task 6');        // (6) -> 同期処理
      resolve('task 8')
  }    
)}

console.log('task 1');              // (1) -> 同期処理

const promiseObj = createPromise();
promiseObj.then((data) => {        // マイクロタスクを登録
    console.log(data);             // (4) ->マイクロタスク
})

setTimeout(() => {                 // マクロタスク(1000ms後)登録 -> 後で実行
    console.log('task 5')          // (5) -> マクロタスクの同期部分 約1000ms後に実行

    const promiseOtherObj = createOtherPromise();
    promiseOtherObj.then((data) => console.log(data))

    setTimeout(() => {             // マクロタスク(この時点から2000ms後)登録 -> 後で実行
        console.log('task 9')      // (9) -> マクロタスクの同期部分 約3000ms後に実行
        console.log('task 10')     // (10) -> マクロタスクの同期部分
    }, 2000)

    console.log('task 7')          // (7) -> マクロタスクの同期部分
}, 1000)

console.log('task 3');             // (3) -> 同期処理

// task 1
// task 2
// task 3
// task 4
// task 5
// task 6
// task 7
// task 8
// task 9
// task 10
タスク 種類 実行順序 タイミング
task 1 同期処理 (1) 直ちに実行
task 2 同期処理 (2) createPromise 内で即時実行
task 3 同期処理 (3) 同上
task 4 マイクロタスク (4) 同期処理終了後、次のマクロタスク前
task 5 マクロタスク (5) 約1000ms 後
task 6 同期処理 (6) マクロタスク内の同期部分
task 7 同期処理 (7) 同上
task 8 マイクロタスク (8) task 7 の直後
task 9 マクロタスク (9) task 5 の中で登録、そこから 2000ms後
task 10 マクロタスク (10) 同上

Promise チェーン

then / catch / finally は常に新しい Promise を返し、その中で return した値が次の then に渡されます。
コールバックで何も return しない場合、次の then には undefined が渡されます。

function sampleAsynchronous() {
  return new Promise<number>(resolve => {
      resolve(100)
  }
)};

const promiseObj = sampleAsynchronous();

promiseObj.then((data: number) => {
  console.log(data)
  return data * 2
}).then((data: number) => {
    console.log(data)
});

// 100
// 200

async 関数

Promise をベースとして非同期処理を簡単に扱うために導入されました。

サンプル

async 関数は Promise を返します。

async function asyncFunc(): Promise<string> {
    return 'Second.';
}

const promiseObj = asyncFunc();
promiseObj.then((data: string) => console.log(data)); --- (2)

console.log('First.')                                 --- (1)

// First.
// Second.

await 式

非同期処理を同期的に扱うための便利な方法として await が導入されました。
await を使わないで非同期処理を同期的に扱うには複雑で技巧的になります。

async function asyncFunc(): Promise<string> {
    const promiseObj = Promise.resolve('First.');
    // then コールバックはマイクロタスクとしてキューに入れられ、同期処理 `console.log('Second.')`の後に処理されます
    // そのため、Second. が先に出力され、その後に First. が出力されます
    promiseObj.then((data: string) => console.log(data) );

    console.log('Second.')

    return 'Third.';
}

const otherPromiseObj = asyncFunc();
otherPromiseObj.then((data: string) => console.log(data)); 

// Second.
// First.
// Third.

await を使用することで First -> Second. -> Third. の順で表示するように改善します。

async function asyncFunc(): Promise<string> {
    const promiseObj = Promise.resolve('First.');
-    promiseObj.then((data: string) => console.log(data) );
+    const data = await promiseObj;
+    console.log(data);
    console.log('Second.')
    return 'Third.';
}

const otherPromiseObj = asyncFunc();
otherPromiseObj.then((data: string) => console.log(data)); 

// First.
// Second.
// Third.

コメント

No comments yet.

コメントの投稿

改行と段落タグは自動で挿入されます。
メールアドレスは表示されません。