deve.K

エンジニアが未来を切り開く。

JavaScriptのPromiseとは その使い方

javascriptのpromise操作と概念

本日はJavaScriptのPromiseの操作とその概念について初学者様に解説致します。

前提の知識とスキルとして、少なくともES6の関数・高階関数・コールバック関数およびJavaScriptオブジェクトに精通している必要があります。

上記を学び終えてない方は、下記で学習できます。

dev-k.hatenablog.com

dev-k.hatenablog.com

dev-k.hatenablog.com

非同期関数

ES6より以前では、非同期コードはコールバックを介して処理されておりました。

非同期プログラミングは、JavaScriptで現在わかっているように、関数がその言語の第一級オブジェクトである場合にのみ実現できます。

第一級オブジェクトとは、解説すると長くなってしまうので、ざっくりで言いますと、変数に格納したり、独自のメソッドやプロパティを保持したり、引数や戻り値として受け渡しの基本的な操作を制限なく、使用できることから、JavaScriptの関数は第一級オブジェクトであることがわかっています。

他の変数と同様に、他の関数に渡すことができます。これがコールバックが生まれた方法となります。

関数を引数として別の関数 (別名 高階関数)に渡すと、関数内で実行が終わった後にそれを呼び出すことができます。

戻り値はなく、値を使用して別の関数を呼び出すだけです。

ですが、コールバックによる課題はあります。

適切に処理されていない場合、コールバックの問題では、『コールバック地獄』または『スパゲッティコード』と呼ばれるものを作成することでそれを実現できます。

基本的に関数内の関数に、関数を入れ子(ネスト)にすることを作成すると、コードを読むのが非常に難しくなり始めます。

returnやthrowを使用する事は出来ず、またエラーの処理を見逃しやすいです。

Promiseは、ネストされ複雑化した非同期コールバックをより適切に処理し扱えるように作成されたのがPromiseの目的となります。

Promise

Promise(約束)はJavaScriptコミュニティによって発明されたものではありません。

この用語は1976年の『ダニエル・P.フリードマン』と『デビッド・ワイズ』という人物に由来しており、彼らがPromiseを提案し、最終的に『ピーター・ヒバード』が呼んだものです。

彼らはコンピュータサイエンスの教授であり、研究はプログラミング言語に焦点を当てており、この分野では著名な作家でもあります。

フリードマンは、プログラミング言語の教科書である『Essentials ofProgrammingLanguages』の筆頭著者でもある、すごい方です。

JavaScriptでのPromiseは、我々の世界での実際の約束に似ています。

私たちが実生活で約束をする際、それは私たちが将来何かをするつもりであるという保証です。

約束は未来のためにしかできないからです。

約束には2つの結果がございますね

約束された時が来た時、それがちゃんと守られているのか

そして守られていないのか。

これは、JavaScriptでのPromiseも同様です。

JavaScriptでPromiseを定義すると、約束の時間が来たときに解決されるか、または拒否されます。

if条件のように思えてくるかもしれません。

しかし、それらの間には大きな違いがあります。

Promiseは約束の時間までは処理される事はありません。

つまりPromiseは、将来使用できるデータを取得することを約束します。

特にif文との違い、それはPromiseでは操作の非同期結果を処理するために使用されます

Promiseを使用すると、非同期のリクエストが完了するまでコードブロックの実行を延期できます。

なので、他の操作を中断することなく実行し続ける事が可能です。

これは、コールバック地獄を脱出するための論理的なステップです。

最も重要な利点は、非同期関数を簡単にチェーンできることになります。

Promiseの状態

まず、Promiseはオブジェクトとなります。

Promiseオブジェクトは3つの状態が存在します。

・Pending: これはPromiseが成功または失敗する前の初期状態となります、つまり作成され待機している間です。

・Resolved: 解決済みの、完了した約束となります。

・Rejected: Promiseが失敗し、エラーがスローされます、約束は果たせなかった時です。

例えばですがこの、Promiseを使用しサーバーにデータを要求すると、データを受信するまでサーバーは保留モードになりますので、作成されたPromiseオブジェクトは待機しているのでPending状態です。

サーバーから情報を取得することに成功した場合、Promiseは正常に解決されます。

状態はResolvedに移行されます。

ただし、情報が取得されない場合、Promiseは拒否された状態になりRejectedに移行し、エラーをスローします。

これら状態は必ず覚えて下さい、必須項目です。

それらを踏まえて、Promiseの作成をしていきましょう。

Promiseの作成

Promiseを作成するには、コンストラクターを使用してPromiseオブジェクトを作成します。

new Promise(function() { 

関数Promiseを呼び出し『new Promise()』で渡すことにより、オブジェクトの新しいインスタンスを作成します。

Promiseとアロー関数を組み合わせることで、コードがさらに読みやすくなりますので、アロー関数を使用します。

const myPromise = new Promise((resolve, reject) => {  
    //何かしらの処理
resolve()

reject()
});

Promiseには2つの引数があります、resolveとrejectです。

先述したように、resolvedは成功(解決)でrejectedは失敗(拒否)です。

状態がresolvedに移行した際は、resolve()メソッドを使用します。

逆に状態がrejectedに移行された際はreject()メソッドになります。

const myPromise = new Promise((resolve, reject) => { 
const condition = true; 

if(condition) {
 setTimeout(function(){
resolve("Promiseが解決、成功しました!"); 
 }, 300);  // Fulfilled状態となる
    } else {    
reject('Promiseが失敗、拒否されました!');  
    }
});

上記のPromiseでは、condition変数がtrueの場合、「Promiseのresolve」を返し、それ以外の場合は、「Promiseのreject」でエラーを返します。

これで、まずは最初のPromiseが作成されました。

現在のPromiseの状態はPending(待機中)です。

console.log(myPromise)
//Promise (Pending)

完了する前に内容をログに記録しようとしたため、約束は『保留中』です。

ただし、その内容をログに記録しようとしても、返されるのはステータスだけです。

正常に解決された場合、ステータスは『解決済み』および『拒否』と表示され、エラーが発生します。

それを実際に使用してみましょう。

上記の作成したPromiseを使用するには、then()メソッドとcatch()メソッドを使用します。

then()はresolveで解決された際に、Fulfilled状態となりthen()が実行されます。

then()メソッドは、作成されたPromiseオブジェクトにハンドラー関数を追加するために使用されます。

一方で、reject()(失敗)が呼び出された際はRejected 状態となり、catch() メソッドが実行される事になります。


const demoSample = function() {
 myPromise
 .then((successMsg) => {
      alert("Success:" + successMsg);
  })
.catch((errorMsg) => { 
      alert("Error:" + errorMsg);
  })
}

demoSample() //呼び出す

作成されたPromise条件は『true』となっていますので、resolveで解決されdemoSample()で呼び出すとアラートで『Promiseが解決、成功しました!』と通知されます。

See the Pen JSのPromise チュートリアル by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


Promise条件をtrueからfalseにし、実行してみて下さい。

情報は取得されず、rejectが呼び出され状態はRejectedに移行しcatch()が実行されエラー文を通知します。

const condition = false; 

//rejectが呼び出される
else {    
 reject('Promiseが失敗、拒否されました!');  
    }// 状態はRejectedとなる

//catchが実行される
 .catch((errorMsg) => { 
 alert("Error:" + errorMsg); 
  })

Promiseは一度だけ解決または拒否できるため、上記は機能します。初回resolveまたはreject呼び出しは、Promiseの結果を決定し、別の要求が終了した後に戻ってくる要求によって引き起こされた、それ以降の呼び出しは無視されます。

つまりresolveとrejectは状態がPendingから移行されてしまったら、一度しか実行しません。

別の例でも見てみましょう。

夕飯の約束を、行くのか、破るのかでJavaScriptで書いていきます。

const promiseDinner = new Promise((resolve,reject) => {  

    goingToDinner = true;  

    if(goingToDinner){  

      resolve('夕食に行きます');   
    }else{ 
      reject('約束が破られた');  

    }  
});

上記のPromiseが実行されると、goingToDinnerの値に基づいて、解決または拒否のステータスが提供されています。

現在の状態はPending状態です。

上記で作成したpromiseを呼び出していきましょう。

promiseDinner.then((result)=>{  
      console.log(result)

  }).catch((result)=>{   
   console.log(result)
})

// 出力: 夕飯に行きます

thenメソッドは、特定のPromiseが実行または解決された場合にのみアクションの一部を実行するメソッドです。

この場合それが解決したとき、つまりは夕食に行った時にresolveが呼び出されFulfilled状態となり、Promiseによって送信された結果を出力しています。

約束が破られた場合または、何かしらのエラーが発生した場合に、状態はresolvedに移行されcatch()メソッドでエラーインスタンスを処理します。

つまり約束が破られ、thenブロックをスキップしてcatchブロックを実行し、出力が『約束が破られた』となります。

このような事から、JavaScriptのPromiseは、非同期操作の最終的な完了または失敗を表すオブジェクトです。

Promiseをチェーンする方法

Promiseをチェーン

Promiseの大きな利点は、Promiseが複数の依存する非同期操作を処理するチェーンを作成できることです

複数での非同期リクエストを順番に呼び出し実行する必要がある場合があります、その際は非常にこのチェーンなら役に立ちます。

最初のPromiseが解決または拒否された後に、別のPromiseを作成します。

新しいプロセスが開始され、チェーンと呼ばれるメソッドで繋げていきます。

Promiseはthen()メソッドを使用し、複数の処理をつなぎ、順番に実行することも可能となります。

JavaScriptの『then()』メソッドは、先述で説明した通りですが、作成されたPromiseオブジェクトにハンドラー関数を追加するために使用されるので『.then()』の結果はハンドラーのチェーンを通過します。

つまり、ハンドラーが値を返すと、そのPromiseの結果となるため、それを使用し次の.thenに繋げていきます。

下記では、単純な乗算(掛け算)を.thenで繋げてチェーンをしています。

const num = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 100);
});

num.then((result) => {
    alert(result);  // 1
    return result * 3;
})

.then((result) => {
    alert(result); // 3
    return result * 5;
})
.then((result) => {
    alert(result); // 15
    return result * 6;
});

See the Pen JSのPromise メソッドチェーン by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


このように、約束の結果は、一連の『.then()』メソッドに渡すことができます。

then()でのこの方法は、多くの場合ではプロミスチェーンと呼ばれます。

書く際の注意点は、thenで繋げる際に下記のように複数回呼び出した場合、チェーンにはなりません。

num.then((result) => {
    console.log(result); // 1
    return result * 3;
})

num.then((result) => {
    console.log(result); // 1
    return result * 5;
})
num.then((result) => {
    console.log(result); // 1
    return result * 6;
});

1つのPromiseを作成し、それに対して複数のハンドラーが存在している事により、繋がられる事はなく個々はそれぞれ独立して実行されている事になります。

Promiseを返す

then()メソッドに値を返すと、新しい値を返してすぐに戻り値に解決されます。

つまり新しいPromiseを返すことができます。

const myPromise = new Promise((resolve, reject) => {

setTimeout(() => resolve("Hi, "), 1000);

}).then((result) => {

alert(result); // Hi,

return new Promise((resolve, reject) => { 
setTimeout(() => resolve(result + "my name is "), 1000);
  });

}).then((result) => { 

alert(result); // Hi, my name is

return new Promise((resolve, reject) => {
setTimeout(() => resolve(result + "Taro."), 1000);
  });

}).then((result) => {

alert(result); // Hi, my name is Taro.

});

上記ではPromiseを新しく作成して、Promiseを返しています。

alert呼び出しの間に1秒の遅延があります。

Promiseを返すことで、非同期処理のチェーンを構築できます。

流れは、最初のthen()が通知され表示されます。

解決されるのは1秒後です。

その結果が、2番目のthen()に渡され、さらに同じ事を示して実行しています。

いくつかのタスクを順番に実行できます。

See the Pen JSのPromise メソッドチェーン Promiseを返す by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.

promiseのthenとfinallyの違い

Promiseを学んでいると、finally()メソッドの存在をいずれ知る事になります。

finallyメソッドはES2018で導入されました。

Promiseが解決または拒否されると、finallyメソッドで指定されたコールバック関数が呼び出されます。

つまりfinally()メソッドもPromiseを返します。

これは、Promiseが正常に実行されたか、Promiseが処理された後に拒否されたかに関係なく、コードを実行する方法を提供します。

例を見てみましょう。

まずは、成功時です。

new Promise( (resolve) => setTimeout(resolve("成功!"), 1000)
).then(value => console.log(value)).finally(() => console.log("Promiseが正常に成功"))
// 出力: 成功!
// 出力: Promiseが正常に成功

正常に処理されています。

では、Promiseが失敗した場合にどうなるか見ていきましょう。

new Promise(() => undefined.get())
   .then(value => console.log(value))
   .catch(error => console.log("失敗!"))
   .finally(() => console.log("Promiseが正常に成功"))

// 出力: 失敗!
//出力: Promiseが正常に成功

See the Pen JSのPromise メソッドチェーン by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


上記を確認した通り、失敗したか成功したかにかかわらず、Promiseはfinallyを実行したことに注意するようにして下さい。

いつ使用するのかですが、場合によってはPromiseチェーンでエラーのキャッチを延期して、他の場所でエラーを処理できるようにすることが可能になります。

特別な理由がない限り、then()メソッドでの代替えとしての使用は避けて下さい。

finallyメソッドはタイムアウトのクリア、参照の無効化、UI状態のリセットなどクリーンアップに使用します。

new Promise((resolve, reject) => reject(0))
  .catch(x => {
    console.log(x); // 0
    throw x;
  })
  .then(x => {
    console.log(x); // 動作処理なし
  })
  .finally(() => {
    console.log("clean up"); // 'clean up'
  });

ですので、最終的な結果に関係なくPromiseが確定したら、メソッド内のリソースをクリーンアップするコードを配置することをお勧めします。

finallyメソッドは他のメソッドと同様にコールバックを受け取りますが、Promiseが解決する値にアクセスできません。

したがって、値を変更することはできません。

より高度な複数のPromise処理

Promiseコンストラクターオブジェクトには、より高度なPromise処理をするための様々なメソッドが含まれています。

単一や複数のPromise処理をする為のメソッドです。

・Promise.all()

・Promise.allSettled()

・Promise.race()

・Promise.any()

多少、複雑になりますがPromise.all()を紹介します。

Promise.all()メソッドは、すべての入力Promiseが解決されたときに解決される単一のPromiseを返します。

const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('最初の約束が解決しました');
    resolve(1);
  }, 1 * 100);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('2つ目の約束が解決しました');
    resolve(3);
  }, 2 * 100);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('3つめの約束が解決しました');
    resolve(5);
  }, 3 * 100);
});

Promise.all([promise1, promise2, promise3]).then(result => {

console.log(`Result: ${result}`);
});

// 出力: 最初の約束が解決しました

// 出力: 2つ目の約束が解決しました

// 出力: 3つめの約束が解決しました

// 出力: Result: 1, 3, 5

すべてのPromiseが解決されると、これらのPromiseの値がthen()配列としてメソッドのコールバックに渡されます。

基本的にPromise.all()は複数の非同期操作からの結果をまとめて出力するのに役立ちます。

See the Pen JSのPromise finallyメソッド by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


JavaScriptのFetchAPIを使用したPromiseベースのHTTPリクエス

当記事ではPromiseを作成し、結果を処理しました。

しかし、Promiseの結果だけを処理する必要がある場合もあります。

たとえば、JavaScriptのFetch APIは、HTTPリクエストを行った後にPromiseを返します。

つまり、then()を使用し結果を処理します。

ダミー用で提供されているAPIを使用します。

const getUsers = fetch('https://jsonplaceholder.typicode.com/users')
.then(response => {

if (response.ok) { 
  console.log ("Request 成功"); 
      return response
} else {
   console.log("Request 失敗");
    } 
})
 .then(response => response.json())
 .then(data => console.log(data)) 

上記では、GETリクエストの結果がどのように処理されるかを示しています。

最後に

then()メソッドとcatch()メソッドを使用すると、JavaScriptでPromiseをチェーンできます。

then()メソッドでは、追加されたPromiseが実行されたときに実行する必要のある操作を指定するために使用されますが、catch()メソッドは、Promiseが拒否された場合を処理します。

コールバック地獄はPromiseの導入によって大幅に軽減されました。

ただし、これらメソッドに大きく依存する必要があります。

ES2017では、Promiseチェーン手法を使用するよりもクリーンなコードを記述できる、Promiseを呼び出す別の方法としての、async/awaitが導入されていることに注意してください。

次回ではこの、比較的新しいasync/awaitでのPromiseを操作する構文を初学者様に説明致します。

then()メソッドとcatch()メソッドを使用してJavaScriptでPromiseをチェーンする手順までの解説は以上となります。

最後までこの記事を読んで頂きありがとうございます。

プライバシーポリシー