天天看點

redux-saga源碼解析-下

這篇文中是對 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 === ) {
      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 = , 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, );
    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, );
  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();
    console.log('1s later');
    yield delay();
    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 的輕量級前端架構)

轉載于:https://juejin.im/post/5bb62b745188255c581ac04a