从 generator 函数 到 redux -saga(三)

289 阅读2分钟

take实现原理

  <body>
    <div id="test">qwe</div>
    <script>

    let $btn = document.getElementById("test");
    //channel是对事件源的抽象,作用是先注册一个take方法,当put触发时,执行一次take方法,然后销毁他。
    function channel() {
        let taker;
        // take 负责往任务队列 里面添加任务监听 
        function take(cb) { 
          taker = cb;
        }
        // put 负责触发给任务流中的next 传参  触发相应的监听
        function put(input) {
          if (taker) {
          const tempTaker = taker;
          taker = null;
          //tempTaker 就是下面的 runTakeEffect 中传进来的next 方法 把put 进来的action传进去
          tempTaker(input);
          }
        }
      
      return{
        put,// 发布者
        take, // 订阅者
      };
     }

    const chan = channel();

    function take() {
      return {
        type: 'take'
      };
    }
    //创建任务队列
    function* mainSaga() {
      const action = yield take();
      const actions = yield take();
    }

    function runTakeEffect(effect, cb) {
      chan.take(input => {
        cb(input);
      });
    }

     // Thunk 函数的自动流程管理 对应 从 generator 函数 到 redux -saga (二) 中的相应部分
     function task(iterator) {
        const iter = iterator();
        function next(args) {
          const result = iter.next(args);
          if (!result.done) {
            const effect = result.value;
            if (effect.type === 'take') {
              runTakeEffect(result.value, next);
            }
          }
        }
        next();
      }

      task(mainSaga);

      let i = 0;
      $btn.addEventListener('click', async()=> {
         const action =`action data${i++}`;
         chan.put(action);
      }, false);	
    </script>
   </body>

fork 和 takeEvery

takeEvery 的作用是每次put 动作执行的的时候 都去调用worker

以下代码可以直接在浏览器运行,执行步骤在代码中做了注释

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>Document</title>
</head>
<body>
	<button id="test">
		Clickme
	</button>
	<div id="result">
	</div>
	<script>
		const $btn = document.querySelector('#test');
		const $result = document.querySelector('#result');
		function channel() {
			let taker;
			
			function take(cb) {
				taker = cb;
			}

			function put(input) {
				if(taker){
				 const tempTaker = taker;
				 taker = null;
				 tempTaker(input);
				}
			}

			return{
				put,
				take,
			};
		}

		const chan = channel();

		let i = 0;
		$btn.addEventListener('click', () => {
			chan.put(`action data${i++}`);
		}, false);

		function take() {
			return {
				type: 'take'
			};
		}

		function fork(cb) {
			return {
				type: 'fork',
				fn: cb,
			};
		}

		function* takeEvery(worker) {
			// worker 就是传进来的函数 
			yield fork(/*标注*/function* () {
				while(true) {
					const action = yield take();
					worker(action);
				}
			});
		}

		function* mainSaga() {
			yield takeEvery(action => {
				$result.innerHTML = action;
			});
		}

		function runTakeEffect(cb){
			//调用chan.take 添加一个监听 put action 的时候执行next(input) 这个时候执行work action  去改变$result的内容
			//再次点击 Clickme 按钮的时候因为task 中的 iterator 还是 上面的标注函数  因为里面部署了while 循环 所以就会源源不断的相应 put 进来的action 执行worker
			chan.take(input => {
				cb(input);
			});
		}
		//fork的作用是启动一个新的task,不阻塞原task执行 
		function runForkEffect(effect, cb) {
			//3,runForkEffect  中调用task 把takeEvery 传进来
			//6.effect 为上面标注的函数的时候 在次调用task 
			task(effect.fn || effect);
			cb();
		}
		 
		function task(iterator) {
		console.log(iterator)
		const iter = typeof iterator === 'function' ? iterator() : iterator;
		function next(args) {
			const result = iter.next(args);
			console.log(result);
			if (!result.done) {
				const data = result.value;
				// 1,iterator 为mainSaga 时候 ->判断data 的值是否为generator 函数   takeEvery 满足此项 
				//4,iterator 为takeEvery 时候 data的 值为{type: "fork", fn: ƒ} fn的值就是fork 中传进来的函数 见 上面标注部分 此时走下面switch 部分逻辑
				//7,iterator 为 标注函数的时候 data的值为 {type: 'take'};
				if (typeof data[Symbol.iterator] === 'function') {
					//2,,iterator 为mainSaga 时候 ->然后执行 把 takeEvery 和next 传给 runForkEffect
					runForkEffect(data, next);
				} else if (data.type) {
					switch (data.type) {
						case 'take':
						//8 把next; 传给runTakeEffect 
							runTakeEffect(next);
							break;
						case 'fork':
							//5,把{type: "fork", fn: ƒ} 传给runForkEffect 
							runForkEffect(data, next);
							break;
						default:
					}
				}
			}
		}
			next();
		}
		//1执行前 调用task部署任务  task 本质上是一个 Thunk函数对generator函数的自动流程管理 
		task(mainSaga);
	</script>
</body>
</html>

takeLatest 和 takeEvery 的区别是:

  • takeEvery允许多个任务实例同时启动,尽管之前还有一个或多个任务尚未结束

  • takeLatest只允许一个任务在执行。并且这个任务是最后被启动的那个。 如果已经有一个任务在执行的时候启动另一个任务,那之前的这个任务会被自动取消。

简单做个图解