现代JavaScript教程 (1).png

    fetch 方法返回的是一个 Promise 对象,而 Promise 里是没有“终止(aborting)”概念的,那么是否表示我们就不能终止 fetch 请求了呢?不是的。

    Javascript 提供了一个用于此目的的特殊对象:AbortController,可以用来终止包括 fetch 方法在内的其他异步任务。

    AbortController 的使用很简单:

    第一步:创建控制器

    1. let controller = new AbortController();

    控制器是一个非常简单的对象。

    • 提供了一个 abort 方法,还有一个 signal 属性
    • 当 abort 方法被调用时
      • 会触发 controller.signal 对象上的 abort 事件
      • controller.signal.aborted 属性值变为 true

    下面举了一个样例(还没用 fetch):

    1. let controller = new AbortController();
    2. let signal = controller.signal;
    3. // triggers when controller.abort() is called
    4. signal.addEventListener('abort', () => alert("abort!"));
    5. controller.abort(); // abort!
    6. alert(signal.aborted); // true

    第二步:将 signal 属性传递给 fetch 选项

    1. let controller = new AbortController();
    2. fetch(url, {
    3. signal: controller.signal
    4. });

    fetch 方法知道如何与 AbortController 进行协作,它会监听 signal 的 abort 事件。

    第三步:退出的话,调用 controller.abort()

    1. controller.abort();

    fetch 感知到 signal 发出退出命令了,就终止了请求。

    fetch 请求终止后,会携带一个 AbortError 异常返回 reject Promise, 我们可以在 try…catch 中做处理:

    1. // abort in 1 second
    2. let controller = new AbortController();
    3. setTimeout(() => controller.abort(), 1000);
    4. try {
    5. let response = await fetch('/article/fetch-abort/demo/hang', {
    6. signal: controller.signal
    7. });
    8. } catch(err) {
    9. if (err.name == 'AbortError') { // handle abort()
    10. alert("Aborted!");
    11. } else {
    12. throw err;
    13. }
    14. }

    AbortController 是可扩展的,我们能使用它同时取消多个 fetch 请求。

    下例,我们并行发起了多个 fetch 请求,调用控制器 abort 方法后,所有请求都会被终止:

    1. let urls = [...]; // a list of urls to fetch in parallel
    2. let controller = new AbortController();
    3. let fetchJobs = urls.map(url => fetch(url, {
    4. signal: controller.signal
    5. }));
    6. let results = await Promise.all(fetchJobs);
    7. // if controller.abort() is called from elsewhere,
    8. // it aborts all fetches

    当然,如果我们还有除 fetch 之外的其他异步任务,需要一起终止,那么也是可以的。我们只要让这些异步任务使用 controller.signal 监听它的 abort 事件就可以了:

    1. let urls = [...];
    2. let controller = new AbortController();
    3. let ourJob = new Promise((resolve, reject) => { // our task
    4. ...
    5. + controller.signal.addEventListener('abort', reject);
    6. });
    7. let fetchJobs = urls.map(url => fetch(url, { // fetches
    8. signal: controller.signal
    9. }));
    10. // Wait for fetches and our task in parallel
    11. let results = await Promise.all([...fetchJobs, ourJob]);
    12. // if controller.abort() is called from elsewhere,
    13. // it aborts all fetches and ourJob

    因此,AbortController 不是仅能用在 fetch 方法上的,它被设计成一个通用对象,用于终止异步任务,只是 fetch 方法天然与它作了集成而已。

    (完)