使用 Taro.request 拦截器时候的一个小坑

275 阅读2分钟

如果你用 Taro 开发微信小程序,并且在发起网络请求前使用了拦截器(包括 Taro 官方提供的两个内置拦截器),就有可能会遇到 RequestTask 对象方法丢失的问题,例如:

TypeError: res.onHeadersReceived is not a function
// 或者
TypeError: res.onChunkReceived is not a function

如果没有使用拦截器,就不会出现上面的问题,RequestTask 中的方法可以正常调用。 原因是拦截器里返回的不是原始的 RequestTask 对象,而是一个新的对象。这个新对象中没有 onHeadersReceived 这些方法。

问题的具体原因

const interceptor = function (chain) {  
    const requestParams = chain.requestParams  
    const { method, data, url } = requestParams  
  
    console.log(`http ${method || 'GET'} --> ${url} data: `, data)  
  
    return chain.proceed(requestParams)  
        .then(res => {  
            console.log(`http <-- ${url} result:`, res)  
            return res  
        })  
}

上面这个是 Taro 官网中给出的自定义拦截器示例。 这个拦截器的返回值是调用了 then() 方法后生成的全新的 Promise 对象,当然不会包含 RequestTask 中的方法。

如何解决

解决方法比较简单,要么返回原始的 RequestTask 对象,要么自己重新挂载一次 RequestTask 的方法。

返回原始的 RequestTask 对象

还是引用官方的示例,改成下面这样,就能在后续的代码中调用 RequestTask 对象的方法:

const interceptor = function (chain) {
  const requestParams = chain.requestParams
  const { method, data, url } = requestParams

  console.log(`http ${method || 'GET'} --> ${url} data: `, data)

  const task = chain.proceed(requestParams)

  task.then((res) => {
    console.log(`http <-- ${url} result:`, res)
    return res
  })

  return task
}

通过查看 Taro.request()chain.process() 方法的源码,会发现 chain.proceed(requestParams) 的返回值已经挂载了微信小程序原生 RequestTask 对象的方法。所以如果拦截器里返回的是 then() 方法的返回值,就会丢失 RequestTask 对象的方法。

Taro.request() 的源码在 packages/shared/src/native-apis.ts 文件, chain.process() 的源码在 packages/taro-api/src/interceptor/chain.ts 文件。

重新挂载 RequestTask 中的方法

Taro 内置的拦截器也有类似的实现,不过只挂载了 abort() 方法。例如 logInterceptor() 的实现:

export function logInterceptor (chain: Chain) {
  const requestParams = chain.requestParams
  const { method, data, url } = requestParams

  // eslint-disable-next-line no-console
  console.log(`http ${method || 'GET'} --> ${url} data: `, data)

  const p = chain.proceed(requestParams)
  const res = p
    .then(res => {
      // eslint-disable-next-line no-console
      console.log(`http <-- ${url} result:`, res)
      return res
    })

  // 这里复制了 abort 方法
  if (isFunction(p.abort)) res.abort = p.abort

  return res
}

所以,如果要使用 RequestTask 中的方法,可以仿照 logInterceptor() 的实现在自定义的拦截器返回之前,加上一句:

if (isFunction(p.onHeadersReceived)) res.onHeadersReceived = p.onHeadersReceived

不过需要注意,由于官方内置的拦截器没有挂载其他 RequestTask 方法,所以如果你使用了官方内置的拦截器,后续的代码依然获取不到 onHeadersReceived() 等方法。有需要的话可以用自定义的拦截器代替。

参考