自己封装一个ajax

105 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

  • 手动实现一个简单的ajax, 通过xhr对象,去写个简单的流程。
function ajax(url,onSuccess,onFailed) {
	const xhr = window.XMLHTTPRequest ? new XMLHTTPRequest() : new ActiveXObject();
	xhr.open("get",url,true);
	xhr.send();
	xhr.onreadystatechange = function () {
		if(xhr.readyState === 4) {
			if(xhr.status === 200) {
				onSuccess && onSuccess(xhr.responseText);
			} else {
				onFailed && onFailed();
			}
		}
	}
} 
  • 封装请求的拦截

添加一些请求的拦截,设置请求超时时间,处理请求成功时的响应,

请求的拦截一般干什么? 请求的拦截一般做一些公共校验,如果通过返回config,resolve后继续往下链

响应的拦截一般干什么? 拦截错误,公共的请求报错,处理返回数据等

 // ajax.html
 const adaptor = function (config) {
     return new Promise((resolve, reject) => {
        const xhr = window.XMLHttpRequest ? new XMLHttpRequest()
            : new ActiveXObject("Mirosoft.XMLHttp");
        xhr.open(config?.method.toUpperCase(), config?.url, true);
        xhr.send();
        
        xhr.timeout = config?.timeout;
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(xhr.responseText);
                } else {
                    reject("request error");
                }
            }
        };
    });
};

// 处理请求成功时的响应
const dispatchRequest = function (config) {
    return adaptor(config).then(
        function (res) {
            return res;
        },
        function (reason) {
            return Promise.reject(reason);
        }
    );
};


const req = function (config) {
    const chain = [dispatchRequest, undefined];

    if (config?.interceptor) {
        chain.unshift(
            config?.interceptor?.fullfilled,
            config?.interceptor?.rejected
        );
    }
    if (config?.adaptor) {
        chain.push(config?.adaptor?.fullfilled, config?.adaptor?.rejected);
    }

    let promise = Promise.resolve(config);

    while (chain.length) {
        promise = promise.then(chain.shift(), chain.shift());
    }

    return promise;
};

req({
    method: "get",
    url: "http://127.0.0.1:5500/data.json",
    interceptor: {
        fullfilled: function (e) {
            console.log("请求被拦截住了", e);
            return e; // 一定要把config返回
        },
    },
    adaptor: {
        fullfilled: function (e) {
            console.log("响应被拦截住了", e);
            return e;
        },
    },
});


// data.json
{
  "code": 0,
  "data": "返回数据了",
  "message": "成功",
  "success": true
}

结果: 在这里插入图片描述

一定要把config返回,因为整体是链式结构的。resovePromise会把return的值resolve掉,作为接下来.then的result,否则拦截过后的页面的请求将获取不到数据和错误原因。

  • 实现一种请求的取消 写一个on/emit去绑定事件,在处理请求的时候处理,从外部得到的config.cancel,在这里用on绑定了abort事件为,cancel传进来的函数,接收一个参数,作为cancel的参数,然后请求中emit执行abort,并将取消请求的方法,报错等等作为一个参数传入emit,而我们最终的目的就是能在外部,去调用这个参数,所以当emit的时候,把取消请求的方法传入绑定的事件中,又通过刚刚on那里穿到cancel,这个时候我们只要将cancel回调中的参数执行就可以取消请求。

我们还做了request的拦截和response的拦截,将请求的响应导入到一个数组chain,将request的拦截放到请求响应的前面,将response的拦截响应放到请求响应之后,这样[interceptorFullFilled,interceptorFullRejected,fullFilled,rejected,adAptorFullfilled,adAptorRejected] 然后我们从数组的前面往后两个一组执行,就可以让请求依次执行request的拦截,请求,response的拦截。

let cc = null;

const mitt = {
    cache: {},
    on: function (name, func) {
      this.cache[name] = func;
    },
    emit: function (name, data) {
      const fn = this.cache[name];
      fn && fn(data);
    },
};

const adaptor = function (config) {
return new Promise((resolve, reject) => {
  let xhr = window.XMLHttpRequest
    ? new XMLHttpRequest()
    : new ActiveXObject("Mirosoft.XMLHttp");

  xhr.open(config?.method.toUpperCase(), config?.url, true);
  xhr.send();

  xhr.onabort = function () {
    xhr = null;
    reject("[abort] request is aborted");
  };

  if (config.cancel) {
    mitt.emit("abort", function () {
      xhr.abort();
      xhr = null;
      reject("【abort】current request is abort.");
    });
  }

  xhr.timeout = config?.timeout;
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        setTimeout(function () {
          resolve(xhr && xhr.responseText);
        }, 5000);
      } else {
        reject("request error");
      }
    }
  };
});
};

// 处理请求成功时的响应
const dispatchRequest = function (config) {
    return adaptor(config).then(
      function (res) {
        return res;
      },
      function (reason) {
        return Promise.reject(reason);
      }
    );
};

const req = function (config) {
const chain = [dispatchRequest, undefined];

if (config?.interceptor) {
  chain.unshift(
    config?.interceptor?.fullfilled,
    config?.interceptor?.rejected
  );
}

if (config?.adaptor) {
  chain.push(config?.adaptor?.fullfilled, config?.adaptor?.rejected);
}

if (config.cancel) {
  mitt.on("abort", function (func) {
    config.cancel(func);
  });
}

let promise = Promise.resolve(config);

while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}

return promise;
};

req({
    method: "get",
    url: "http://127.0.0.1:5500/3browser/01/data.json",
    interceptor: {
      fullfilled: function (e) {
        console.log("请求被拦截住了", e);
        return e; // 一定要把config返回
      },
    },
    adaptor: {
      fullfilled: function (e) {
        console.log("响应被拦截住了", e);
        return e;
      },
    },
    cancel: function (onCancel) {
      cc = onCancel;
    },
});
setTimeout(function () {
    cc && cc();
}, 2000);