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」を実行する
- メソッドチェーンでつながれた
then
やcatch
「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
あるいは、必要に応じて、全体をtry
~catch
で囲んでもいいでしょう。
その場合には、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