task

task 并不是一个直接暴露给外部的 api,它是在调用其它 api 之后返回的对象,这些 api 有:

  • middleware.run
  • fork
  • runSaga 还有一些 api 接收 task 作为参数,这些 api 有:
  • cancel
  • join

源码地址

packages/core/internal/newTask.js

解析

newTask 顾名思义它会创建一个 task 对象,这个对象包含一些 fields 和 methods,我们接下来就分别介绍一下官网里面介绍的 methods。 在介绍主要的方法之前我先简单介绍一下前置的参数和逻辑:

  • status: 表示 task 的状态
  • taskResult: 表示 task 最终执行的结果
  • taskError: 表示 task 执行过程中产生的错误
  • deferredEnd: 参考下方 toPromise 方法
  • cancelledDueToErrorTasks: 由于执行过程中报错而被取消的 tasks 数组
  • queue: 任务队列,包含主任务和分支任务,关于 queue 的相关解析可以看 forkQueue 这篇
export default function newTask(env, mainTask, parentContext, parentEffectId, meta, isRoot, cont) {
  let status = RUNNING
  let taskResult
  let taskError
  let deferredEnd = null

  const cancelledDueToErrorTasks = []

  const context = Object.create(parentContext)
  const queue = forkQueue(
    mainTask,
    function onAbort() {
      cancelledDueToErrorTasks.push(...queue.getTasks().map(t => t.meta.name))
    },
    end,
  )

  function cancel() {
    // ......
  }

  function end(result, isErr) {
    // ......
  }

  function setContext(props) {
    // ......
  }

  function toPromise() {
    // ......
  }

  const task = {
    // fields
    [TASK]: true,
    id: parentEffectId,
    meta,
    isRoot,
    context,
    joiners: [],
    queue,

    // methods
    cancel,
    cont,
    end,
    setContext,
    toPromise,
    isRunning: () => status === RUNNING,
    isCancelled: () => status === CANCELLED || (status === RUNNING && mainTask.status === CANCELLED),
    isAborted: () => status === ABORTED,
    result: () => taskResult,
    error: () => taskError,
  }

  return task
}

isRunning

这个方法很简单就是判断一下当前的 status === RUNNING 若任务还未返回或抛出了一个错误则为 true。

isRunning: () => status === RUNNING

isCancelled

这个方法是判断任务是否已经取消。 主要判断两个条件是否成立:

  • status === CANCELLED: 如果当前任务的状态为 CANCELLED 则返回 true
  • (status === RUNNING && mainTask.status === CANCELLED) 如果主任务取消并且当前任务正在执行,返回 true
isCancelled: () => status === CANCELLED || (status === RUNNING && mainTask.status === CANCELLED),

result

直接将任务的返回值返回

result: () => taskResult,

error

直接将错误对象返回

error: () => taskError,

toPromise

这个方法的目的是返回一个 promise 对象,如果 status === ABORTED 也就是任务报错了,则返回一个 rejected 状态值为 taskError 的 promise 对象,如果没有报错并且 status !== RUNNING 也就是任务正确执行完毕则返回一个 fulfilled 状态值为 taskResult 的 promise 对象。

注意:

这个方法依赖一个方法 deferred() 去生成一个 promise 对象,这个方法很简单,你可以去 pacages/deferred/src/index.js 去看它的源码。

function toPromise() {
  if (deferredEnd) {
    return deferredEnd.promise
  }

  deferredEnd = deferred()

  if (status === ABORTED) {
    deferredEnd.reject(taskError)
  } else if (status !== RUNNING) {
    deferredEnd.resolve(taskResult)
  }

  return deferredEnd.promise
}

cancel

cancel 方法只有在 status === RUNNING 也就是 task 正在执行的时候才会执行取消操作。

  • 首先设置 status = CANCELLED
  • 其次执行 queue.cancelAll() 关于这个方法的详情可以去看 forkQueue 这篇
  • 最后执行 end(TASK_CANCEL, false) 这里面也就是最后一步比较复杂,它调用了另一个方法 end,我们会在下面讲解 end 方法。
/**
  This may be called by a parent generator to trigger/propagate    cancellation
  cancel all pending tasks (including the main task), then end the current task.
  父生成器可以调用它来 触发/传播 取消
  取消所有挂起的任务(包括主任务),然后结束当前任务。

  Cancellation propagates down to the whole execution tree held by this Parent task
  It's also propagated to all joiners of this task and their execution tree/joiners
  取消将向下传播到此父任务所持有的整个执行树
  它还传播到此任务的所有参与者及其执行树/参与者

  Cancellation is noop for terminated/Cancelled tasks tasks
  取消是 noop 对于 终止/取消 的任务的任务
**/
function cancel() {
  if (status === RUNNING) {
    // Setting status to CANCELLED does not necessarily mean that the task/iterators are stopped
    // 将状态设置为 CANCELLED 并不一定意味着 任务/迭代器 被停止
    // effects in the iterator's finally block will still be executed
    // //迭代器的 finally 块中的 effects 仍然会执行
    status = CANCELLED
    queue.cancelAll()
    // Ending with a TASK_CANCEL will propagate the Cancellation to all joiners
    // 以 TASK_CANCEL 结尾将把取消传播给所有的参与者
    end(TASK_CANCEL, false)
  }
}

end

end 方法里面有一个大的判断 !isErr 将整个代码分成两大块:

  • 无 error
  • 有 error
function end(result, isErr) {
  if (!isErr) {
    // ......
  } else {
    // ......
  }
  // ......
}

无 error

如果 isErr 为假表示没有错误产生,则去判断 result === TASK_CANCEL,如果成立说明是 task 被取消了,修改 status,给 taskResult 赋值,如果有 deferredEnd 对象,则执行 deferredEnd.resolve(result),如果不成立说明 task 已经完成,操作和上面一致。

if (!isErr) {
  // The status here may be RUNNING or CANCELLED
  // If the status is CANCELLED, then we do not need to change it here
  // 这里的状态可能正在运行或被取消
  // 如果取消了状态,那么我们不需要在这里更改它
  if (result === TASK_CANCEL) {
    status = CANCELLED
  } else if (status !== CANCELLED) {
    status = DONE
  }
  taskResult = result
  deferredEnd && deferredEnd.resolve(result)
}

有 error

  • 首先修改了 status 为 ABORTED
  • 剩下的代码都和 sagaError 有关,这个是一个处理 sagaError 里面错误的模块,会记录错误信息,最终返回给 onError 方法,我觉得并不是太重要,所以就不做深入展开了。
  • 给 taskError 赋值
  • 处理 deferredEnd 对象,这两步和上面无 error 的情况是一致的
if (!isErr) {
  // ......
} else {
  status = ABORTED
  sagaError.addSagaFrame({ meta, cancelledTasks: cancelledDueToErrorTasks })

  if (task.isRoot) {
    const sagaStack = sagaError.toString()
    // we've dumped the saga stack to string and are passing it to user's code
    // we know that it won't be needed anymore and we need to clear it
    // 我们已经将 saga 堆栈转储为 string 并将其传递给用户代码
    // 我们知道它将不再需要,我们需要清理它
    sagaError.clear()
    env.onError(result, { sagaStack })
  }
  taskError = result
  deferredEnd && deferredEnd.reject(result)
}

处理 joiners

处理完 !isError 之后,还有些逻辑需要处理:

  • 调用 task.cont 这个 cont 就是 newTask 方法的最后一个参数,我看了一下,它的值不是 noop 就是 cb,当然 noop 是不会做任何事情的,如果是 cb 那么就是继续执行 task 之外的代码。
  • 接着处理 joiners,首先遍历所有 joiners 然后执行所有 joiner 的 cb,最后将 task.joiners 置空,joiners 和 join 方法相关,join 方法会将当前 task push 到 taskToJoin.joiners 里面,当 taskToJoin 执行完毕才会执行外部的 task,这也是 join 方法的目的:创建一个 Effect 描述信息,用来命令 middleware 等待之前的一个分叉任务的结果。
task.cont(result, isErr)
task.joiners.forEach(joiner => {
  joiner.cb(result, isErr)
})
task.joiners = null
最后更新时间: 6/26/2019, 10:33:23 AM