You need to enable JavaScript to run this app. 脱离jQuery实现自定义ajax后链式调用无法等待异步请求完成如何解决
优惠活动
大模型
产品
解决方案
定价
更多

脱离jQuery实现自定义ajax后链式调用无法等待异步请求完成如何解决

问题描述

更新:新增简化演示jsfiddle,https://jsfiddle.net/47sfj3Lv/3/,用极少量代码复现问题。

我正在尝试脱离jQuery重构代码,原先填充表格的代码如下:

var hb = new hbLister(url: '#attributes.listURL#')
  .getData(#url.page#, #url.per#)
  .search(searchColumn, searchParam)
  .render();
  • hbLister负责初始化相关配置
  • getData执行$.ajax请求
  • search执行console.log('filtering data')并对JavaScript对象应用搜索条件
  • render将结果渲染到页面

核心要求是search必须等getData内的ajax请求完成后再执行。

现有实现及问题

我目前写的ajax构造器简化代码如下:

let ajax = function (options, hooks, headers) {
  let that = this;

  // enforce parameter types
  // copy parameters to this.base
  this.base = { options: options, hooks: hooks, headers: headers }

  return function (url, options, data, hooks, headers) {
    // enforce variable types
    // merge options and hooks with their base.counterparts

    headers = new Headers(Object.assign({}, that.base.headers, headers));
    options.headers = headers;

    return fetch(url, options)
      .then(response => {
        return response.json().then(json => {
          console.log('processing');
          if (response.ok) {
            // it's omitted here but the Base functions are defined
            // in the constructor parameters
            hooks.successBase(json, response.status, response.headers);
            hooks.success(response.json, response.status, response.headers)
          } else {
            hooks.failureBase(json, response.status, response.headers);
            hooks.failure(response.json, response.status, response.headers)
          }
        })
      });
  }
}

预期用法是:

let tableLister = new ajax()

之后getData内调用:

tableLister = tableLister(hb.url, // url
  { type: "GET" }, // options
  config.data, // data
  { success: successFn } // hooks, success being the callback
)

原先jQuery版本执行顺序为:先输出processing,再输出filtering data,符合预期。但现在基于fetch的版本执行顺序为先输出filtering data、报错,之后才输出processing,链式调用.search(...).render()不会等待ajax请求完成。

我判断解决方案应该用到async和await,但尝试后无法生效,尝试的代码如下:

return await (async function () {
    console.log('fetching')
    let fetcher = await fetch(url, options);

    console.log('getting json');
    return await fetcher.json().then((json) => {
      console.log('have json, doing hooks');
      if (fetcher.ok) {
        let argXHR = { json: json}
        hooks.successBase(argXHR, hooks.params);
        hooks.success.forEach(v => v(argXHR, hooks.params));
        hooks.afterSend(argXHR, hooks.params);
      } else {
        let argXHR = { json: json,}
        hooks.failureBase(argXHR, hooks.params);
        hooks.failure.forEach(v => v(argXHR, hooks.params));
        hooks.afterError(argXHR, hooks.params);
      }
      console.log('finished hooks')
    })
  }())

无论怎么调整,链式调用都会在await执行完成前继续向下执行。

我目前用XMLHttpRequest实现的同步版本可以正常支持链式调用.getData().search(...).render(),因为请求完成、回调执行前函数不会返回,但我更希望用fetch实现需求,相关代码如下:

let xhr = new XMLHttpRequest()

  let urlParams = [];
  Object.keys(data).forEach((v) => urlParams.push(v + '=' + encodeURIComponent(data[v])));
  urlParams = urlParams.join('&')

  xhr.open(options.method, options.url, false);
  xhr.onreadystatechange = function(state) {
    if (this.readyState == 4) {
      let json = JSON.parse(xhr.responseText)
      hooks.successBase(json, xhr.status, xhr.getResponseHeader);
      hooks.success.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
    }
  }
  xhr.onerror = function() {
    let json = JSON.parse(xhr.responseText)
    hooks.failureBase(json, xhr.status, xhr.getResponseHeader);
    hooks.failure.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
  }
  for (h in headers) {
    xhr.setRequestHeader(h, headers[h])
  }
  xhr.send(urlParams)

请问如何基于fetch实现支持等待异步完成的链式调用?

解决方案

你之前的async/await尝试没生效,核心原因是普通函数内return await返回的本质还是Promise实例,你调用时没有等待这个Promise状态变更就直接执行后续的search方法,才会出现顺序错误。同步XHR虽然能实现需求,但会阻塞主线程,体验很差,不符合现代前端开发规范。

推荐用内部维护Promise队列的封装方案,完全保留你之前的链式调用写法,自动按顺序执行异步操作:

class TableLister {
  constructor(config) {
    this.url = config.url;
    // 内部维护执行队列,初始为已完成状态
    this.taskQueue = Promise.resolve();
    // 存储请求返回的原始数据
    this.rawData = null;
  }

  getData(page, per) {
    // 把请求逻辑追加到队列末尾
    this.taskQueue = this.taskQueue.then(async () => {
      const requestUrl = `${this.url}?page=${page}&per=${per}`;
      const res = await fetch(requestUrl, { method: 'GET' });
      this.rawData = await res.json();
      console.log('processing');
    });
    // 始终返回实例本身,支持链式调用
    return this;
  }

  search(column, param) {
    this.taskQueue = this.taskQueue.then(() => {
      console.log('filtering data');
      // 对已拿到的rawData执行筛选逻辑
      this.rawData = this.rawData.filter(item => item[column]?.includes(param));
    });
    return this;
  }

  render() {
    this.taskQueue = this.taskQueue.then(() => {
      // 你的渲染逻辑,把rawData插入到页面表格中
      console.log('rendering', this.rawData);
    });
    // 可选:添加全局错误捕获
    this.taskQueue.catch(err => console.error('执行错误:', err));
    return this;
  }
}

调用方式和你原先的写法完全一致:

const hb = new TableLister({url: '#attributes.listURL#'})
  .getData(#url.page#, #url.per#)
  .search(searchColumn, searchParam)
  .render();

如果不想修改现有封装逻辑,也可以调整调用方式,用async/await等待异步完成:

async function loadTableData() {
  const hb = new hbLister({url: '#attributes.listURL#'});
  // 等待getData的异步请求完成
  await hb.getData(#url.page#, #url.per#);
  hb.search(searchColumn, searchParam);
  hb.render();
}
loadTableData();

内容的提问来源于stack exchange,提问作者Regular Jo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.16 22:06:04