JavaScript/TypeScriptメモ

Promiseの基本~Promiseの挙動サンプル、コード例

Promiseとは?

Promiseとは、JavaScript(TypeScript)で、非同期処理を統一的に扱うインタフェースです。

このページでは、TypeScriptを前提にPromiseの使い方をまとめます。

Promiseオブジェクトの生成

1. 返り値がPromiseの関数呼び出し

const ret = fetch(`http://abc.com/`);

Promiseオブジェクトは、呼び出した関数内で行われます。

2. Promise.resolve

const resolved = Promise.resolve(`resolve value`);

Promiseの前にnewは不要です。

3. Promise.reject

const rejected = Promise.reject(new Error(`rejected`));

これも、Promiseの前にnewは不要です。

4. 非同期処理をPromiseでラップ

setTimeout関数の例です。

正常終了する場合にはresolve、異常終了する場合にはrejectを呼び出すのが基本です。

また、new Promise<...>の部分は、型変数を指定しましょう。今回は、stringを指定しています。

逆に、コールバック関数内のresolve、rejectの型は自動で付きますので、型変数は不要です。

//正常終了時の返り値の型(今回の場合は「string」)をPromiseの型変数に指定
const promise1 = new Promise<string>((resolve, reject) => {
  setTimeout(() => {
    try {
      //非同期処理内の処理はtry~catchで囲むのが基本
      resolve('return value'); //処理の返り値はresolveに渡す
    } catch (e) {
      reject(e); //エラーはrejectに渡す
    }
  }, 1000);
});

Promise作成時の実行順

下記ソースコードを実行して、実行順を確認してみましょう。

const OrderOfExecutionTestSub = () => {
  const promise = new Promise<string>((resolve, reject) => {
    console.log('2'); // Promise内部 
    setTimeout(() => {
      console.log('5'); // 非同期処理本体       resolve('resolved');
    }, 0);
  }).then((value) => {
    console.log(value);
    console.log('6'); // then内部  });
  console.log('3'); // Promise作成直後};

const OrderOfExecutionTestMain = () => {
  console.log('1'); // プログラム開始  OrderOfExecutionTestSub();
  console.log('4'); // プログラム終了};

OrderOfExecutionTestMain();
// 1
// 2
// 3
// 4
// 5
// resolved
// 6

つまり、次のような順序で実行されるということになります。

  • new Promiseで与えたコールバック関数内部の処理「2」は、その場で実行される(=常に「同期的」に実行する
  • Promise内部の非同期処理(setTimeout内部)は後回しにして、「3」→「4」と、いったんプログラムの末尾まで実行する
  • プログラム末尾までたどりついた後、Promise内部の非同期処理「5」を実行する
  • メソッドチェーンでつながれたthencatch「6」を実行する

Promise内部のエラーハンドリング

さきほどのnew Promiseで与えたコールバック関数内部の処理「2」で発生したエラーは、Promiseのcatch句で補足できます。

const errorCatchable = () => {
  const promise = new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      resolve('some value');
    }, 0);
    throw new Error('catchable');  }).catch((e) => {
    console.log('catched');
  });
};

errorCatchable();
// catched

一方で、Promise内部の非同期処理(setTimeout内部)「5」で発生したエラーは補足できず、そのままエラーになります

const errorNotCatchable = () => {
  const promise = new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      throw new Error('not catchable');    }, 0);
  }).catch((e) => {
    console.log('catched');
  });
};

errorNotCatchable();
// Error: not catchable
//    at Timeout._onTimeout (C:\nodejs\src\Promise.ts:X:X)
//    at listOnTimeout (internal/timers.js:531:17)
//    at processTimers (internal/timers.js:475:7)
//  ...

異常終了を表す場合には、冒頭で説明したようにrejectを使いましょう。

const errorCatchable2 = () => {
  const promise = new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('catchable'));    }, 0);
  }).catch((e) => {
    console.log('catched');
  });
};

errorCatchable2();
// catched

あるいは、必要に応じて、全体をtrycatchで囲んでもいいでしょう。

その場合には、catch句でrejectを呼び出しましょう。

const errorCatchable3 = () => {
  const promise = new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      try {        //各種処理
        throw new Error('catchable');
      } catch (e) {        reject(e); // rejectを呼び出す      }    }, 0);
  }).catch((e) => {
    console.log('catched');
  });
};

errorCatchable3();
// catched

then、catch、finallyメソッド

Promiseオブジェクトを作った後の処理は、thenメソッド、catchメソッド、finallyメソッドで書いていきます。

基本的に、正常系の処理はthenメソッド内に書いていきます。

import fetch from 'node-fetch';

const fetchZipcode = () => {
  const yuubinApi = fetch(
    'https://zipcloud.ibsnet.co.jp/api/search?zipcode=7830060'
  )                            //APIにアクセスしてresponseを取得
    .then((res) => res.json()) //正常系。responseからjsonを取得
    .then((json) => {
      console.log(json);       //正常系続き。jsonを表示する
    })
    .catch((err) => {
      console.log(err);        //エラーの場合の処理
    });
};

fetchZipcode();
//{
//  message: null,
//  results: [
//    {
//      address1: '高知県',
//      ...
//      prefcode: '39',
//      zipcode: '7830060'
//    }
//  ],
//  status: 200
//}

以下、Promiseの挙動を知るための例です。

1. resolveされた値は「then」メソッドで受け取る

const promiseTestResolved = () => {
  const promise = Promise.resolve('resolved');

  promise.then((value) => {
    console.log(value);
  });
};

promiseTestResolved();
//resolved

2. rejectされた値は「catch」メソッドで受け取る

const promiseTestRejected = () => {
  const promise = Promise.reject('rejected');

  promise.catch((err) => {
    console.log(err);
  });
};

promiseTestRejected();
//rejected

途中にthenメソッドを挟んでもかまいません。一番近いcatchメソッドで、rejectされた値を受け取れます。

const promiseTestRejected2 = () => {
  const promise = Promise.reject('error');
  promise
    .then((value) => { // thenは無視される
      console.log(`resolved`);
      console.log(value);
    })
    .catch((err) => { // rejectされた値を受け取る      console.log(`rejected`);      console.log(err);    });};

promiseTestRejected2();
//rejected
//error

3. then、catchメソッドは常に新しいPromiseオブジェクトを返す

const isSameObject = () => {
  const promise1 = Promise.resolve('resolved1');

  const promise2 = promise1.then(() => {
    return 'resolved2';
  });

  console.log(promise1 === promise2);};

isSameObject();
//false

4. then、catchメソッドで普通の値をreturn→resolve

thenメソッドの中で、普通の値(=Promise.reject以外の値)をreturnで返すことで、新しく値をresolveすることができます。

そのresolveした値は、後に続くthenメソッドで、その値を使うことができます。

const resolveInThen = () => {
  const promise = Promise.resolve('')
    .then(() => {
      return 100; // 普通の値でOK(Promise<number>が返り値になる)    })
    .then((value) => {
      console.log(value); // 100
      return Promise.resolve('abc'); // Promise.resolveを使ってもOK    })
    .then((value) => {
      console.log(value); // 'abc'    });
};

resolveInThen();
//100
//abc

catchメソッドが間に入っていても無視されます。

const resolveInThen2 = () => {
  const promise = Promise.resolve('')
    .then(() => {
      return 100; 
    })
    // rejectされていないので無視される
    .catch(() => {      console.log(`ignored1`);    })    .then((value) => {
      console.log(value);
      return Promise.resolve('abc');
    })
    // 直前でrejectされていないので無視される
    .catch(() => {      console.log(`ignored2`);    })    .then((value) => {
      console.log(value);
    });
};

resolveInThen2();
//100
//abc

catchメソッドの中からも、普通の値をreturnで返すことで、新しく値を「resolve」することができます。

catch内から単にreturnをしただけでは「reject」にはならないことに注意しましょう

const resolveInCatch = () => {
  const promise = Promise.reject('error')
    // 直前でresolveされていないので無視される
    .then((v) => {
      console.log(v);
      return 'ignored';
    })
    .catch((e) => {
      console.log(e);
      return 'return value of catch';    })
    .then((value) => {
      console.log(value); // return value of catch    });
};

resolveInCatch();
//error
//return value of catch

5. then、catchメソッド内で「return Promise.reject(...)」または「throw」→reject

thenメソッドのreturnでPromise.reject(...)を値をreturnすると、「reject」することができます。「reject」した結果は、一番近いcatchで受け取ることができます。

const rejectInThen = () => {
  const promise = Promise.resolve('')
    .then(() => {
      return Promise.reject('error');    })
    .catch((err) => {
      console.log(err); // error    });
};

rejectInThen();
//error

thenメソッドでthrow errorしても「reject」できます。

const rejectInThen2 = () => {
  const promise = Promise.resolve('')
    .then(() => {
      throw 'error';    })
    .catch((err) => {
      console.log(err); // error    });
};

rejectInThen2();

catchメソッド内でrejectしたいときも、上の2つのどちらかの方法を取りましょう。

rejectした値は、その次のcatchメソッドで使えます。

const rejectInCatch = () => {
  const promise = Promise.reject('')
    .catch(() => {
      return Promise.reject('error');    })
    .catch((err) => {
      console.log(err); // error    });
};

rejectInCatch();

6. finallyメソッドは必ず実行される

finallyメソッドは必ず実行されます。なお、finallyメソッドは、引数を取れません

const finally1 = () => {
  const promise = Promise.resolve('resolved')
    .then((value) => {
      console.log(value);
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {      console.log('finally');    });};

finally1();
//resolved
//finally

7. finallyメソッド以降のthen、catchメソッドは実行されない

finallyメソッド以降の、thenメソッド、catchメソッドは実行されません。

const finally2 = () => {
  const promise = Promise.resolve('resolved')
    .then((value) => {
      console.log(value);
      return 'then1';
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {      console.log('finally');    })    .then((value) => {
      // 絶対に実行されない
      console.log(value);
    });
};

finally2();
//resolved
//finally