前回ES2016構文に慣れたところで、今回は関数型プログラミングについて学びます。
忘れていたjsの基本
PHPer的に慣れない下記二点に注意。
- 関数外にある変数は すべてグローバルスコープ である..そして値も書き換えられる。クロージャにはPHP無名関数で言うところの
use
が(言語仕様上不要なため)存在しない - クロージャは、宣言時点ではなく 実行時点の状態で 動作する
/**
* php
*
* greetingは後から変更できない。
*/
$greeting = "Hello";
$introduction = function ($name) use ($greeting) {
echo "{$greeting}!! My name is {$name}.";
};
$introduction('dia'); // "Hello!! My name is dia."
$greeting = "Bonjour";
$introduction('ruby'); // "Hello!! My name is ruby."
/**
* javascript (ES2016)
*
* greetingを後から変更できる、そして関数宣言時点で存在しなくても良い
*/
const introduction = name => {
console.log(`${greeting}!! My name is ${name}.`);
}
let greeting = 'Hello';
introduction('dia'); // "Hello!! My name is dia"
greeting = 'Bonjour';
introduction('ruby'); // "Bonjour!! My name is dia"
// ※これが関数実行時のコンテキスト(前後の文脈)で変化する..ということ、かな?
下記サイトでサンプルコードの動作確認ができます。
関数型プログラミングを学ぶ
参考記事: Qiita - JavaScriptで関数型プログラミングの入門
関数型の特徴
- 変数の再代入は少ないほどよい(なるべく
const
使おう) - 関数の参照透過性が保たれている..
引数が同じであれば何回その関数を実行しても結果が変わらない
ということ - 関数は必ず値を返却すること
// Good
function hello() {
return 'Hello'
}
console.log(hello()); // Hello
// Bad
function hello() {
console.log('Hello');
}
hello();// Hello
- 関数に副作用がないこと
- 外側の変数を変更しない
- 参照渡しの引数の値を変更しない
// Good 副作用がない
var z = 2;
function add(x, y) {
return x + y;
}
console.log(add(z, 3)); // 5
console.log(add(z, 3)); // 5
// Bad 副作用がある
var z = 2;
function add(x, y) {
z = x + y;
return z;
}
console.log(add(z, 3)) // 5
console.log(add(z, 3)) // 8
- 関数を値として扱う..引数に関数を渡せる。高階関数という
非同期処理を理解する
用語
そのまえに用語
- context(コンテキスト).. jsの記事を読んでいるとすごくよく出てくる。説明が難しいが一言でいうと『文脈』・・・前後関係、「何に」対して処理を(または「何を」)行っているのかということ。
- Promise.. ES2015から使える非同期処理用の何か。
.then().then().then()
とチェインすることができる。Qiita - Promiseを使おう
ローカルデバッグ(ステップ実行)方法
Visual Studio Codeをインストールし、ソースを開いた状態でF5を押すとデバッグできる。
単純なスクリプトだと特に設定は必要ない・・・はず。
非同期処理
Qiita - 俺たちはJavaScriptの非同期処理とどう付き合っていけば良いのだろうか
- なぜ非同期?
- ユーザのアクションに対して処理が終わるまで待ち続けるUIなんて使い物にならない
- 並列処理が行える
- いつ非同期?
- HTTPのリクエスト
- タイマーによる処理
- ブラウザ操作におけるイベント処理
- DATABASEの操作
- コールバック地獄に陥りがち
- 次の方法で非同期処理を実装できる(詳細は参照元記事)
- Callback Hell.. 最もシンプルで低レベルな記述方法。ベストではないが基本的にどの環境でも動く
- Then Chains.. ES6の
Promise
を使う方法 - Generator..
Generator
yield
co
を使う方法 - Async/Await.. 今後標準化されそうなライブラリを使う方法
Then Chains
の例に習って練習
ES2016でPromiseを使いながらペペロンチーノを作ろう!
* お湯を沸かして(5分)パスタを茹でる(10分)
* ペペロンチーノソースを作る(3分)
* 両方できたら混ぜて炒める(2分)
* 皿に盛る(1分)
Promiseについては下記を参考に作っていきます。
Qiita - 今更だけどPromise入門
Promiseメモ
- Promiseを利用するにはPromiseをnewでインスタンスを作る
- 作ったインスタンスはそのままリターンする
- 実際の処理はCallbackを渡す
- 成功したらresolve, 失敗したらrejectを呼ぶ
setTimeout()
は一定時間後に特定の処理を行う1回だけ行うもの
ペペロンチーノつくるよ!
const startTime = new Date();
// 料理名
const name = 'ペペロンチーノ';
// レシピ(ステップと所要時間)
const recipe = {
wakasu: 5,
yuderu: 10,
sauce: 3,
mazeru: 2,
moru: 1
}
// 調理手順をひとつ実行する
const cook = step => {
return new Promise((resolve, reject) => {
let costTime = recipe[step] * 100;
setTimeout(() => {
// 実行
console.log(`${step}を行いました。${costTime}ms かかりました。`);
// 「成功」と返却する
resolve(true);
}, costTime);
});
};
// 完了メッセージ
const finish = () => {
console.log(`${name}できました!!`);
const endTime = new Date();
console.log(`作るのに ${endTime - startTime}ms かかりました。`);
};
// 調理開始
Promise.all([
// ここは並列処理
cook('wakasu').then(() => {
return cook('yuderu');
}),
cook('sauce'),
]).then(() => {
return cook('mazeru');
}).then(() => {
return cook('moru');
}).then(finish);
// -- console --
// "sauceを行いました。300ms かかりました。"
// "wakasuを行いました。500ms かかりました。"
// "yuderuを行いました。1000ms かかりました。"
// "mazeruを行いました。200ms かかりました。"
// "moruを行いました。100ms かかりました。"
// "ペペロンチーノできました!!"
// "作るのに 1806ms かかりました。"
・・・最後のほうがあんまり美しくない気がするけど、こういうものなのだろうか。
おわりに
今回はPromiseを使った関数型プログラミングを勉強しました。元ソースを見ながら作ったため、実際の業務的な処理を書けるようになるには練習を重ねるしかなさそうです。しかし今回で少しだけ関数型の世界を垣間見れた気がしました。