コールバックモードの欠点とPromise
ES6
に先立って、非同期処理における動作順序を保証するためにerror−firstコールバックモードが使用される.node.js
において、fs
モジュールを使用してファイルを読み取る際に使用されるreadFile()
関数は、error−firstコールバックモードを使用する良い例である.しかし使い続けて多くの欠点を発見し、それを解決するためにPromiseが誕生した.
いったい何が問題なのか、error-first callbackモードをPromiseモードに変更する方法もあります!🙂
callback hell
コールバックhellは、非同期処理が必要な操作順序を保証しようとしたときに発生する.
例えばtext 1.txt -> text_2.txt -> text_3.txt順にファイルの内容を出力します.
const fs = require('fs');
fs.readFile('./text_1.txt', (err, data) => {
if(err) {
console.error(err);
}
console.log(data.toString());
});
fs.readFile('./text_2.txt', (err, data) => {
if(err) {
console.error(err);
}
console.log(data.toString());
});
fs.readFile('./text_3.txt', (err, data) => {
if(err) {
console.error(err);
}
console.log(data.toString());
});
実行結果1. text_1.txt content!
2. text_3.txt content!
3. text_2.txt content!
非同期処理は同期ストリームに従わないため、非同期操作の実行順序は保証されません.😥
ファイルの容量がそれぞれ異なると、より予想外の結果が得られる可能性があります.
今回はコードを修正して、順番に実行させます!
const fs = require('fs');
fs.readFile('./text_1.txt', (err, data) => {
if(err) {
console.error(err);
}
console.log(data.toString());
fs.readFile('./text_2.txt', (err, data) => {
if(err) {
console.error(err);
}
console.log(data.toString());
fs.readFile('./text_3.txt', (err, data) => {
if(err) {
console.error(err);
}
console.log(data.toString());
});
});
});
実行結果1. text_1.txt content!
2. text_2.txt content!
3. text_3.txt content!
上述したように、コードを変更することで、非同期動作の順序を保証することができる.しかし、順番に実行するファイルが10個を超えるとしたら?🤔
このようにerror-first callbackモードを使用すると、コードの
indent
が深まり、現在はデータを出力する簡単な操作ですが、インポートしたデータを四半期ごとに異なる操作を行うと、その可読性が低下します.それ以外に,例外処理を行うためには,
try...catch
構文を各callback関数に追加する必要があり,多くの欠点が存在し,これらの欠点を解決するためにPromiseが出現した.Promise
Promiseはerror-firstコールバックモードの多くの欠点を補うために生まれた機能である.
しかし、これはコールバックを全く使用しないのではなく、非同期処理のためのコールバックに現れる可能性のある様々な欠点を補う機能である.
Promiseの特徴は,非同期処理を実行できることであるが,結果を直接コールバック関数として渡すのではなく,後で処理できることである.
また、Promiseは、
new Promise()
コンストラクション関数によってPromiseオブジェクトを作成して処理するので、Promiseオブジェクトを他の関数に渡して処理してもよい.では、Promiseについて詳しくご紹介しましょう.😆
Promiseの状態
Promiseには、次の3つの状態があります.
pending
Promiseの作成から
fulfilled
またはrejected
のステータスfulfilled
Promiseで
resolve()
関数が呼び出されると、fulfilled
状態になります.rejected
Promiseで
reject()
関数が呼び出されると、rejected
状態になります.resolve()
またはreject()
の関数が呼び出されると、このPromiseは解析済みPromiseと呼ばれ、解析済みPromiseがresolve()
またはreject()
を再び呼び出すとしても、その状態は変化しない.要するに、Promiseは最終的に
fulfilled
またはrejected
のいずれかの状態にすぎない.Promiseオブジェクトの利用
Promiseが登場してから、error-firstコールバックモードを使用する関数はPromiseをサポートするようになりました.
ただし、Promiseオブジェクトを返さない関数は
new Promise()
コンストラクション関数を使用してPromiseオブジェクトを使用することもできるので、心配することはありません.fs.readFile()
関数をPromiseオブジェクトに変換する例で、Promiseの使い方を理解します!🙂const fs = require('fs');
getText('./text_1.txt')
.then(data => console.log(data)) // 파일 내용이 있을 경우 실행
.catch(err => console.error(err)); // 파일 내용이 없거나 파일을 불러오지 못 한 경우 실행
function getText(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if(err || data.toString() === '')
reject(new Error('no contents'));
resolve(data.toString());
})
});
}
(fs
モジュールではPromiseがサポートされていましたが、error-firstコールバックモード関数をPromiseオブジェクトとして作成する方法を示すために、わざわざこのように例を書きました.)これで、
getText()
関数を呼び出すとPromiseオブジェクトが返され、Promiseオブジェクトのパラメータを表示すると、resolve
およびreject
を持つcallback関数を使用して非同期処理するタスクを囲むことがわかります.resolve
およびreject
もコールバック関数であり、関数が正常に動作している場合はresolve()
を呼び出し、失敗した場合は条件に従ってreject()
を呼び出す.そして、戻るPromiseオブジェクトの
then()
メソッドを用いてresolve()
を処理し、catch()
メソッドを用いてreject()
を処理することができる.then()
メソッドの2番目のパラメータもreject()
を扱うことができますが、これではthen()
コールバックで発生する例外を見つけることができないので、catch()
メソッドを使いましょう!😣このような利点はcallbackの呼び出しを2回防止できることであるが,Promiseの真の価値はCheningにある.
Promise chaining
Promiseチェーンがないと仮定し、Promiseを使用してcallback hellが発生する可能性のあるコードを作成します.
比較のためにcallback hellで使用する例は同じです!
const fs = require('fs');
getText('./text_1.txt').then(data => {
console.log(data);
getText('./text_2.txt').then(data => {
console.log(data);
getText('./text_3.txt').then(data => {
console.log(data);
}, (err) => console.error(err))
}, (err) => console.error(err))
}, (err) => console.error(err));
function getText(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if(data.toString() === '') reject(new Error('err'));
resolve(data.toString());
})
});
}
Promiseを使用していますが、indent
がerror-firstコールバックモードを使用しているのとあまり変わらないように見えます...😥しかし,Promiseチェーンを用いて非常に簡潔なコードを記述することができる.
const fs = require('fs');
getText('./text_1.txt')
.then(data => { console.log(data); return getText('./text_2.txt');})
.then(data => { console.log(data); return getText('./text_3.txt');})
.then(data => console.log(data))
.catch(err => console.error(err));
function getText(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if(data.toString() === '') reject(new Error('err'));
resolve(data.toString());
})
});
}
Promiseもオブジェクトであるため、then()
関数の戻り値で別のPromiseオブジェクトを返し、コードを簡潔にします.また,複数の非同期タスクに対する
reject()
処理もPromiseチェーンを用いて1つのcatch()
で処理できる!このようにPromiseを用いることでerror−firstコールバックモードの多くの欠点を解決できる.
JavaScript
の特性のため、非同期処理はほとんど必要であり、error−firstコールバックに基づくAPI
の多くはPromiseに基づいて再構築されているため、Promiseオブジェクトの正確な理解が必要である.不足や間違いがあれば、指摘してください.ありがとうございます.🤗
参考資料
Using promises - JavaScript | MDNhttps://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Using_promises
Promise()作成者-JavaScript|MDNhttps://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
Promise - JavaScript | MDNhttps://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise
Reference
この問題について(コールバックモードの欠点とPromise), 我々は、より多くの情報をここで見つけました https://velog.io/@jaeung5169/callback-패턴의-단점과-Promiseテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol