可取消cancelable的promise实现机制

在mobx6中可以通过引入flow来实现取消promise,在实际工作中应用场景很多,如react更新页面时取消正在进行的promise,那么,这具体是如何实现的呢?

核心代码

export type CancellablePromise = Promise & { cancel(): void }

export const flow: Flow = Object.assign(
  function flow(arg1, arg2?) {
    // @flow
    if (isStringish(arg2)) {
      return storeDecorator(arg1, arg2, "flow")
    }
    // flow(fn)
    if (__DEV__ && arguments.length !== 1)
      die(`Flow expects 1 argument and cannot be used as decorator`)
    const generator = arg1
    const name = generator.name || ""

    // Implementation based on https://github.com/tj/co/blob/master/index.js
    const res = function () {
      const ctx = this
      const args = arguments
      const runId = ++generatorId
      const gen = action(`${name} - runid: ${runId} - init`, generator).apply(ctx, args)
      let rejector: (error: any) => void
      let pendingPromise: CancellablePromise | undefined = undefined

      const promise = new Promise(function (resolve, reject) {
        let stepId = 0
        rejector = reject

        function onFulfilled(res: any) {
          pendingPromise = undefined
          let ret
          try {
            ret = action(
              `${name} - runid: ${runId} - yield ${stepId++}`,
              gen.next
            ).call(gen, res)
          } catch (e) {
            return reject(e)
          }

          next(ret)
        }

        function onRejected(err: any) {
          pendingPromise = undefined
          let ret
          try {
            ret = action(
              `${name} - runid: ${runId} - yield ${stepId++}`,
              gen.throw!
            ).call(gen, err)
          } catch (e) {
            return reject(e)
          }
          next(ret)
        }

        function next(ret: any) {
          if (isFunction(ret?.then)) {
            // an async iterator
            ret.then(next, reject)
            return
          }
          if (ret.done) return resolve(ret.value)
          pendingPromise = Promise.resolve(ret.value) as any
          return pendingPromise!.then(onFulfilled, onRejected)
        }

        onFulfilled(undefined) // kick off the process
      }) as any

      promise.cancel = action(`${name} - runid: ${runId} - cancel`, function () {
        try {
          if (pendingPromise) cancelPromise(pendingPromise)
          // Finally block can return (or yield) stuff..
          const res = gen.return!(undefined as any)
          // eat anything that promise would do, it's cancelled!
          const yieldedPromise = Promise.resolve(res.value)
          yieldedPromise.then(noop, noop)
          cancelPromise(yieldedPromise) // maybe it can be cancelled :)
          // reject our original promise
          rejector(new FlowCancellationError())
        } catch (e) {
          rejector(e) // there could be a throwing finally block
        }
      })
      return promise
    }
    res.isMobXFlow = true
    return res
  } as any,
  {
    annotationType_: "flow" as const
  }
)

function cancelPromise(promise) {
  if (isFunction(promise.cancel)) promise.cancel()
}

原理

  • 通过generator+promise实现,利用generator的next可以被return来中断
  • 构造promise的cancel方法
  • 在promise中构造关键的next方法

参考

  • mobx actions
  • Canceling promises with Generators in ES6 Javascript
  • mobx flow源码

你可能感兴趣的:(可取消cancelable的promise实现机制)