redux-saga に入る前に
redux-saga
のサンプルを見ていて、function*
をみた瞬間、これなんぞ!となったのでメモ。
ジェネレーター
function*
はジェネレータ関数。
他の言語でも、見たことはあったけど、ちゃんと使ったことがなかった。
こんな感じに使える。
function* basic() {
yield 1;
yield 2;
yield 3;
}
const basicGenerator = basic();
console.log(basicGenerator.next());
console.log(basicGenerator.next());
console.log(basicGenerator.next());
関数を途中で止めて、再開みたいな感じで、初めてみたときは衝撃的だった。
このジェネレータの機能を使うと、非同期処理をわかりやすく書けるとのこと。
とはいえ初めて見たとき、全然わかりやすくなかったので、順を追って書く。
まずは、非同期処理を順に追って実行したいけど、できていない処理
function timer(message, ms) {
setTimeout(() => {
console.log(message);
}, ms);
}
function execTimer() {
timer('first', 3000);
timer('second', 1000);
}
execTimer();
コールバック地獄 →Promise→aysnc await と学んできたけど、ジェネレータを使うという選択肢があった。
function* execTimer() {
yield timer('first', 3000);
yield timer('second', 1000);
}
const timerGenerator = execTimer();
timerGenerator.next();
timerGenerator.next()
を一回だけ実行すると、execTimer
の最初の yield の処理が実行される。
今のままだと、first
だけ出力されて終わってしまうので、first
が終わった後に、再度timerGenerator.next()
を実行すればいいはず。
ということでこんな感じにtimerGenerator.next()
を実行する関数を渡します。
const timerGenerator = execTimer(() => timerGenerator.next());
timerGenerator.next();
受け取った関数をtimer
で実行するようにする。
function timer(message, ms, next) {
setTimeout(() => {
console.log(message);
next();
}, ms);
}
function* execTimer(next) {
yield timer('first', 3000, next);
yield timer('second', 1000, next);
}
これで、ジェネレータを使って非同期処理を順列に書くことができるようになった。
最初わかりにくかったけど、こう書いてみるとわかりやすいと思うようになってきた。不思議。
ジェネレータ + Promise + co
co
というライブラリを使うと、上記のジェネレーターの非同期処理をnext
を渡さなくともできるようになるとのこと。
https://github.com/tj/co
import co from 'co';
function timer(message, ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(message);
resolve();
}, ms);
});
}
function* execTimer() {
yield timer('first', 3000);
yield timer('second', 1000);
}
co(execTimer);
next
を渡してないけど、想定通りの挙動になる。
co
のコードをなんとなく読んでみると、ものすごく機能を抜粋するとこんな感じになっているのだと思う。
本家だとapply
とかcall
とか使っているけど、なぜそれが必要なのかわかってない。
const myCo = generator => {
const iterator = generator();
consumePromise(iterator);
function consumePromise(iterator) {
let promise = iterator.next();
if (promise.done) return;
promise.value.then(() => consumePromise(iterator));
}
};