JavaScriptを基本からまとめてみた【14】【Promise】

##はじめに

####学習するに至った経緯
2020年より、未経験からエンジニアへの転職を目指し、某プログラミングスクールへ通う。入学後、『Ruby』を未経験から学ぶ人が多いのと『Ruby』の求人が思っていた以上に少ないので、卒業後、フロントエンドのエンジニアを目指す事に。
Javascriptの学習した事を言語化し、認識の深化による備忘録として記載。

##非同期処理とは
『非同期処理』とは、ある処理が実行されてから終わるまで待たずに、次に控えている別の処理を行うこと。JavaScriptはシングルスレッドでしか動かない性質があるため、複数の処理を並列で走らせることができない。そのため効率的に処理をするために考えられた仕組みが非同期処理と呼ばれるもの。

##Promiseとは
『Promise』とはJavaScriptにおいて、非同期処理の操作が完了したときに結果を返すもの。Promiseは処理が実行中の処理を監視し、処理が問題なく完了すればresolve、反対に問題があればrejectを呼び出してメッセージを表示する。

##Promiseの状態
- apending： 初期状態、実行中。成功も失敗もしていない。
- fulfilled： 処理が完了した状態。then メソッドを呼び出す。
- rejected： 処理が失敗した状態。then メソッドと catch メソッドを呼び出す。
※ resolve() が呼ばれると、fulfilled の状態になります。<br>
※ rejected() が呼ばれると、rejected の状態になります。

##Promiseの基本的な書き方
Promiseはresolveとreject、ふたつの関数を引数に取る。

- resolve：処理が成功したときのメッセージを表示する関数
- reject：処理が失敗したときのメッセージを表示する関数

```main.js
new Promise(function(resolve, reject) {
    resolve('成功');
});
new Promise(function(resolve, reject) {
    reject('失敗');
});
```
##Promise の then

then()  は、 Promise のインスタンスの状態が fulfilled となったときに実行する関数を登録できるインスタンスメソッドで、then() は、以下のように定義される。

```
Promise.prototype.then(onFulfilled, onRejected)
```

- onFulfilled : fulfilled の状態のとき（resolve が呼ばれたとき）に実行される関数
- onRejected : rejected の状態のとき（reject が呼ばれたとき）に実行される関数


##thenを使ってコールバック処理を実行する
thenを使ってコールバック関数を実行する書き方は、すこし複雑で、promise.thenを実行すると、処理した結果の"Promise成功！"という文字列がresultに引き渡される。そのためconsole.logでresultを表示すると、"Promise成功！"という文字がコンソール上に表示される。
　そしてサンプルコードの実行結果に注目すると、console.log(result)のあとにconsole.log(“先に出力”)を実行しているにも関わらず、”先に出力”というメッセージが”Promise成功！”よりも先に出力されている。

```main.js
var sample = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve();
    }, 1000);
});
sample.then(function(value) {
    console.log("Promise成功!");
});
console.log("先に出力");
```

##Promiseの処理を連結させる方法
『チェイン』を使うことで、複数の処理を連結させることができる。『最初の処理が成功した場合は次の処理に自動的に移る･･･』を繰り返すことで、より複雑で高度な処理をコーディングできる。コードの中身は、getNumber関数は渡された数字numを受け取り、numに対して3を2回加算するというもので、.thenを3回連続で使用している箇所があるが、これが『チェイン』と呼ばれるもの。このように、Promiseでは複数の処理を連続で処理させることが可能。

```main.js
function getNumber(num) {
    return new Promise(function(resolve, reject) {
        // numが3以上ならnumを返し、3未満なら"Falied!"のメッセージを返す
        if (num >= 3) {
            setTimeout(function() {
                resolve(num);
            }, 1000);
        } else {
            reject("Falied!");    
        }
    });  
}
// 今回は3を渡しているので、resolveから3が返ってくる
getNumber(3).then(function(result) {
    console.log(result);
    //numに3を加算して、getNumberに返している
    return result + 3;
}).then(function(result) {
    //上と同じ処理の繰り返し。これがチェイン
    console.log(result);
    return result + 3;
}).then(function(result) {
    // 最終結果として、numに6を加算した数を表示
    console.log(result);
}, function(err){
    // 3未満の場合はrejectが呼び出され、"Falied!"のメッセージが表示される
    console.log(err);
});
```

##allを使って複数の非同期処理を同時に行う
『all』を使うことで、複数の非同期処理を同時に実行することができる。
書き方は以下の通り。

```
Promise.all(iterable).then(function(message) {
    // 結果を表示する処理
}
```

『all』はすべての非同期処理がresolveされたタイミングで結果を返す。
下記のサンプルコードだと、promise1は300ms後, promise3は500ms後に処理されることになっているが、1000ms後にresolveされるpromise2が完了したタイミングで結果を返す。
下記のサンプルコードは、
console.log("First");
console.log("Second");
setTimeout(function(){console.log("Third")}, 600);
Promise.all([promise1, promise2, promise3])~
の順番で結果を返していることがわかる。

```main.js
// promise1, 2, 3を別個作成する
let promise1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve();
    }, 300);
});
let promise2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve();
    }, 1000);
});
let promise3 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve();
    }, 500);
});
// Promise.allを使って、3つのpromiseを同時に実行している
Promise.all([promise1, promise2, promise3]).then(function() {
    console.log("Fourth");
});
console.log("First");
setTimeout(function(){
    console.log("Third");
}, 600);
console.log("Second");
```

##Promise の catch

reject()  とは、 Promise のインスタンスの状態が rejected となったときに実行する関数を登録するインスタンスメソッドです。

```main.js
Promise.prototype.catch(onRejected)
```
- onRejected : rejected の状態のとき（reject が呼ばれたとき）に実行される関数

##catchを使ったエラーハンドリングのやり方
catchを使うことで、チェインを実行している最中にエラーが発生してもエラーを捕まえることができる。
下記のコードでは2つ目のチェインに入った段階でエラーを発生させているので、3を加算した結果である「6」を返す前にエラーメッセージを表示させる。

```main.js
function getNumber(num) {
    return new Promise(function(resolve, reject) {
        if (num >= 3) {
            setTimeout(function() {
                resolve(num);
            }, 500);
        } else {
            reject("Falied!");    
        }
    });  
}

// 今回は3を渡しているので、resolveから3が返ってくる
getNumber(3).then(function(result) {
    console.log(result);
    return result + 3;
}).then(function(result) {
    // 2つ目の処理でエラーを発生させる
    throw new Error('エラー！失敗しました');
    console.log(result);
    return result + 3;
}).then(function(result) {
    console.log(result);
    // catchを使うことで、エラーが発生した時点でエラーメッセージを返す
}).catch(function(e) {
    console.log('error: ', e);
});
```

##Promiseのまとめ
- Promiseは処理が成功すればresolveを返し、失敗ならrejectを返す
- Promiseを使うと、ネストを深くせずに非同期処理のコールバック関数が書ける
- チェインを使うことで、複数の処理を連続して処理できるようになる
- allはすべての非同期処理が完了した時点で、resolveを返す
- catchを使い、エラーが発生した時点でエラーを返すようにできる

#####これらの特性から、非同期処理時はPromiseを使うようにすると、コード記述時にミスやエラーが発生する可能性を減らすことができる。


##参考サイト
[【JavaScript】初心者にもわかるPromiseの使い方]
(https://techplay.jp/column/581)
[【JavaScript】 async/await で非同期処理をわかりやすく記述する]
(https://rightcode.co.jp/blog/information-technology/javascript-async-await)