這篇文中是對 redux-saga原始碼解析 的補充,可以幫助大家更加全面的瞭解redux-saga。主要探索一下 channel, 和redux-saga中的取消任務 想要具體分析一下channel並不是因為多麼複雜,而是工作中可能會用到
channel
在前一篇文章中說到其實redux-saga支援三種channel,分別是channel, eventChannel, multicastChannel。multicastChannel 在前一篇文章已經分析過。這裡就看一下另外兩個。
channel
接受一個buffer物件,返回 channel 物件 buffer的作用:當沒有任務監聽器時,先把事件快取起來,如果後面有新的任務監聽器建立,就立即執行任務。
export function channel(buffer = buffers.expanding()) {
let closed = false
let takers = []
function put(input) {
// 如果 channel 已經關閉,返回
if (closed) {
return
}
// 當沒有任務監聽器時, 快取在 buffer 裡
if (takers.length === 0) {
return buffer.put(input)
}
// 正常執行任務
const cb = takers.shift()
cb(input)
}
function take(cb) {
// 如果 channel 已經關閉並且buffer為空,則對任務傳送END訊號
// 也可以看出來,即使 channel 關閉,buffer只要不為空,還是可以正常建立任務監聽器的
if (closed && buffer.isEmpty()) {
cb(END)
} else if (!buffer.isEmpty()) {
// buffer不為空的話 直接執行
cb(buffer.take())
} else {
// 正常建立任務監聽器
takers.push(cb)
cb.cancel = () => remove(takers, cb)
}
}
// 清空buffer裡的任務
function flush(cb) {
//...
if (closed && buffer.isEmpty()) {
cb(END)
return
}
cb(buffer.flush())
}
// 當關閉channel時,終止所有任務監聽器
function close() {
//...
if (!closed) {
closed = true
if (takers.length) {
const arr = takers
takers = []
for (let i = 0, len = arr.length; i < len; i++) {
const taker = arr[i]
taker(END)
}
}
}
}
return {
take,
put,
flush,
close,
};
}
複製程式碼
eventChannel
eventChannel 是對 channel的封裝,主要區別是 channel.put 函式交給subscribe函式驅動
export function eventChannel(subscribe, buffer = buffers.none()) {
let closed = false
let unsubscribe
const chan = channel(buffer)
const close = () => {
if (is.func(unsubscribe)) {
unsubscribe()
}
chan.close()
}
unsubscribe = subscribe(input => {
if (isEnd(input)) {
close()
closed = true
return
}
chan.put(input)
})
if (!is.func(unsubscribe)) {
throw new Error('in eventChannel: subscribe should return a function to unsubscribe')
}
unsubscribe = once(unsubscribe)
if (closed) {
unsubscribe()
}
return {
take: chan.take,
flush: chan.flush,
close,
}
}
複製程式碼
example:
// 這個channel剛建立就被關閉了
eventChannel(emitter => {
emitter(END)
return () => {}
})
複製程式碼
cancel
function* test(action) {
try {
yield call(delayUtil, 5000);
yield put({ type: 'ADD_TODO', text: action.text });
} finally {
if (yield cancelled()) {
console.log('cancelled');
}
}
}
function* rootSaga() {
const action = yield take('ADD_TODO_SAGA');
const task = yield fork(test, action);
yield call(delayUtil, 4000);
yield cancel(task);
}
sagaMiddleware.run(rootSaga)
複製程式碼
這裡首先監聽了ADD_TODO_SAGA事件,在4s之後又取消了任務,由於在子任務中延時了5s,所以 ADD_TODO 並沒有被丟擲。
cancel effect
function cancelSingleTask(taskToCancel) {
if (taskToCancel.isRunning()) {
taskToCancel.cancel()
}
}
複製程式碼
可以看到呼叫task的cancel方法。
function cancel() {
if (task._isRunning && !task._isCancelled) {
task._isCancelled = true
taskQueue.cancelAll()
}
}
複製程式碼
實際執行的就是 taskQueue.cancelAll() 在這裡取消了任務及其子任務。
cancelled effect
function runCancelledEffect(data, cb) {
cb(Boolean(mainTask._isCancelled))
}
複製程式碼
直接將_isCancelled作為結果
由於取消任務是在父執行緒上作的,所以應該通知到子執行緒上。
原理是當迭代器執行return方法時,iterator.return()
函式就會跳過try剩下的語句,直接進入finally程式碼塊。這裡改造一下之前的自動流程控制demo
const delay = (ms) => {
return new Promise((res) => {
setTimeout(res, ms);
});
}
function *main() {
try {
console.log('begin');
yield delay(1000);
console.log('1s later');
yield delay(2000);
console.log('end');
} finally {
console.log('finally');
}
}
function autoRun(gfunc) {
const gen = gfunc();
function next() {
const res = gen.next();
gen.return('cancel');
if (res.done) return;
res.value.then(next);
}
next();
}
autoRun(main);
// begin
// finish
複製程式碼
可以看到直接進入了finally程式碼塊。
總結
這邊文章主要是對前一篇文章的補充,探索了一下我比較感興趣的兩個點,也希望讀者看完之後會有所幫助。 接下來我會分析一下dva(基於 redux、redux-saga 和 react-router 的輕量級前端框架)