这节来写proc。proc主要功能是执行iterator。我们将一个generator的运行用一个task对象来表示。proc运行返回值即为task,代表当前的saga任务。taks内部有个taskQueue队列,管理着当前主任务mainTask以及子任务(fork任务,后面会讲到)。每个saga运行后都会维护一个task树。
mainTask具有一个cont方法,mainTask结束或者运行出错时会执行cont方法,来将结束消息或者错误消息传递给task。
saga代码调用结构:
iterator需要手动的去调用iterator.next()
方法才会往下执行。同时,iterator.next()
是可以传入参数的,传入的参数会被当作是上一个yield表达式的结果。例如:
1 2 3 4 5 6 7 8
| function* gen{ let result = yield 3; console.log(result); }
let it = gen(); it.next(); it.next(4);
|
为了满足iterator的运行,采用递归来消耗iterator。proc有个next()
方法,该方法会被不断调用以来消耗iterator。
在generator中,yield
语句后面可以跟普通方法、Promise、或者另一个generator方法。yield后面跟着的其实就是我们期望执行的副作用effect,每种effect都会有个对应的effectRunner来执行。
如果yield
后面跟的依然是一个generator,那么需要运行这个generator并将返回的iterator传入proc,另起一个任务来执行这个generator。
1 2 3 4 5 6 7
| function* gen(){ let result = yield subGen(); } function* subGen(){ let result = yield 4; return result }
|
runner去执行对应effect之后,需要将执行结果返回,并继续执行主程序,所以effectRunner中需要传入一个cb(callback),来告诉effectRunner副作用执行完毕后该干啥。
effect和task 是可取消的,每个effectRunner需要给传入的cb设置一个cancel方法,告诉主任务如何取消当前的effect;每个task也都需要设置一个cancel方法,以便能够被取消。对于附属task,附属task需要将自己的取消方法设置给自己的主回调(mainCb)上。
需要注意一点,已完成的effect不能再取消,已取消的effect也不能再继续下去。在digestEffect()
中我们会保证这个互斥关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| import effectRunner from './effectRunner.js'; import deferred from './utils/deferred'; import {is} from './utils/is'; import newTask from'./task'; import noop from './utils/noop'; import * as taskStatus from './utils/taskStatus'
function proc(env, iterator, mainCb, name){ let mainTask = {status: taskStatus.RUNNING, name}; let def = {}; let promise = new Promise((resolve, reject) => { def.resolve = resolve; def.reject = reject; }).catch(e => console.log(e)) def.promise = promise; let task = newTask(def, name, mainTask, mainCb); mainTask.cancel = function(){ if(mainTask.status === taskStatus.RUNNING){ mainTask.status = taskStatus.CANCELLED; next('cancel_task'); } } mainCb.cancel = task.cancel; next(); return task function next(arg, isErr){ try{ let result; if(isErr){ result = iterator.throw(arg); } else if(arg === 'cancel_task'){ next.cancel(); result = is.func(iterator.return) ? iterator.return('cancel_task'): {value:'cancel_task'done: true} } else { result = iterator.next(arg); } if(!result.done){ digest(result.value, next) } else { mainTask.cont(result.value, isErr); } }catch(e){ mainTask = taskStatus.ABORTED; mainTask.cont(e, true); } } function runEffect(effect, currCb){ currCb.cancel = noop; if(is.iterator(effect)){ proc(env, effect, currCb) } else (is.promise(effect)){ effect.then(currCb, error => { currCb(error, true) }) } else if(effect && effect.type) { let effectRunner = effectRunnerMap[effect.type]; effectRunner(effect, currCb) } else { currCb(effect) } } function digestEffect(effect, cb){ let settled = false; function currCb(res, isErr){ if(settled){ return } settled = true; cb.cancel = noop; cb(res, isErr) } cb.cancel = () => { if(settled){ return } settled = true; currCb.cancel(); currCb.cancel = noop; } runEffect(effect, currCb) } }
|
本节代码地址:https://github.com/xusanduo08/easy-saga/tree/master/%E5%86%99%E4%B8%80%E4%B8%AAredux-saga-2