fetch 函数必须 this 与 window 相同!

590 阅读2分钟

解决 :Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation

背景

写业务的过程中,项目中 axios 添加了一些业务的请求头,导致普通 get 会变成复杂请求,获取资源会跨域。所以在业务中使用原生的 fetch 先过渡一下。

后续业务迭代,代码需要兼容小程序,所以 JSON 数据的请求需要进行兼容,小程序中没有 fetch,需要自己用 wx.request 自己 fork 一下。

onMounted(() => {
    dataControl.value = new OfficialDeliverControl(props.param, {
        gatewayAxiosInstance: gatewayAxiosInstance,
        fetchInstance: isWeapp ? wxFetch : fetch,
    });
    deliverForms.value = (dataControl.value?.data || []).filter(item => item.show);

});

执行后业务报错,且 JSON 数据也没有拿到。

image.png

寻找问题

Google 一下,在这篇问答中找到了答案:

Why does bind fix "failed to execute 'fetch' on 'Window' illegal invocation" error? - Stack Overflow

For some internal purpose, the fetch function must have this be the same as window. When you create your own object and just assign the fetch function as one of its properties, the fetch function has no access to the window object as this.

The fetch specification describes things the window might be used for. You might be able to make your original code work by setting no-window, but I have not tested that.

翻译过来:

fetch 函数必须 thiswindow 相同。当您创建自己的对象并仅将 fetch 函数分配为其属性之一时, fetch 函数无法像 this那样访问 window对象。

并给出了一个规范的地址: fetch.spec.whatwg.org/#concept-re…

image.png

可以确定 fetchthis 指向一定是 window

解决问题

修改代码 fetch => fetch.bind(window)

onMounted(() => {
    dataControl.value = new OfficialDeliverControl(props.param, {
        gatewayAxiosInstance: gatewayAxiosInstance,
        fetchInstance: isWeapp ? wxFetch : fetch.bind(window),
    });
    deliverForms.value = (dataControl.value?.data || []).filter(item => item.show);

});

✅ 问题得到了解决。

继续探索

写一个 js 最小 DEMO 复现验证一下

class MyControl {
  fetchInstance = null;
  DATA_URL = "http://127.0.0.1:5500/data.json";
  
  constructor(options = {}) {
    this.fetchInstance = options.fetchInstance || fetch;
  }

  getData() {
    this.fetchInstance(this.DATA_URL)
      .then((res) => res.json())
      .then((data) => {
        console.log(data);
      });
  }
}

function init() {
  const myControl = new MyControl({});
  myControl.getData();
}
init();

在浏览器执行,会复现相同的报错。

image.png

如果我们加以修改,把 getData 放到 init 函数中,通过调整代码的位置将函数执行的作用域改成 window 下,验证一下。

class MyControl {
  fetchInstance = null;
  DATA_URL = "http://127.0.0.1:5500/data.json";
  constructor(options = {}) {
    this.fetchInstance = options.fetchInstance || fetch;
  }

  getFetchInstance() {
    return this.fetchInstance;
  }
}

function init() {
  const myControl = new MyControl({});
  const fetchInstance = myControl.getFetchInstance();
  fetchInstance("http://127.0.0.1:5500/data.json")
    .then((res) => res.json())
    .then((data) => {
      console.log(data);
    });
}
init();

执行代码,我们获取到了我们想要的数据。

image.png

结论

fetchthis 需指向 window