JavaScript前端超时异步操作完美解决过程

目录
  • 如果一段代码久久不能执行完成,会怎么样?
  • Axios 自带超时处理
  • 处理 fetch() 超时
  • 万物皆可超时

自从 ECMAScript 的 Promise ES2015 和 async/await ES2017 特性发布以后,异步在前端界已经成为特别常见的操作。异步代码和同步代码在处理问题顺序上会存在一些差别,编写异步代码需要拥有跟编写同步代码不同的“意识”。

如果一段代码久久不能执行完成,会怎么样?

如果这是同步代码,我们会看到一种叫做“无响应”的现象,或者通俗地说 —— “死掉了”;但是如果是一段异步代码呢?可能我们等不到结果,但别的代码仍在继续,就好像这件事情没有发生一般。

当然事情并不是真的没发生,只不过在不同的情况下会产生不同的现象。比如有加载动画的页面,看起来就是一直在加载;又比如应该进行数据更新的页面,看不到数据变化;

再比如一个对话框,怎么也关不掉 …… 这些现象我们统称为 BUG。但也有一些时候,某个异步操作过程并没有“回显”,它就默默地死在那里,没有人知道,待页面刷新之后,就连一点遗迹都不会留下。

Axios 自带超时处理

使用 Axios 进行 Web Api 调用就是一种常见的异步操作过程。通常我们的代码会这样写:

try {
    const res = await axios.get(url, options);
    // TODO 正常进行后续业务
} catch(err) {
    // TODO 进行容错处理,或者报错
}

这段代码一般情况下都执行良好,直到有一天用户抱怨说:怎么等了半天没反应?

然后开发者意识到,由于服务器压力增大,这个请求已经很难瞬时响应了。考虑到用户的感受,加了一个 loading 动画:

try {
    showLoading();
    const res = await axios.get(url, options);
    // TODO 正常业务
} catch (err) {
    // TODO 容错处理
} finally {
    hideLoading();
}

然而有一天,有用户说:“我等了半个小时,居然一直在那转圈圈!”于是开发者意识到,由于某种原因,请求被卡死了,这种情况下应该重发请求,或者直接报告给用户 —— 嗯,得加个超时检查。

幸运的是 Axios 确实可以处理超时,只需要在 options 里添加一个 timeout: 3000 就能解决问题。如果超时,可以在 catch 块中检测并处理:

try {...}
catch (err) {
    if (err.isAxiosError && !err.response && err.request
        && err.message.startsWith("timeout")) {
        // 如果是 Axios 的 request 错误,并且消息是延时消息
        // TODO 处理超时
    }
}
finally {...}

Axios 没问题了,如果用 fetch() 呢?

处理 fetch() 超时

fetch() 自己不具备处理超时的能力,需要我们判断超时后通过 AbortController 来触发“取消”请求操作。

如果需要中断一个 fetch() 操作,只需从一个 AbortController 对象获取 signal,并将这个信号对象作为 fetch() 的选项传入。大概就是这样:

const ac = new AbortController();
const { signal } = ac;
fetch(url, { signal }).then(res => {
    // TODO 处理业务
});

// 1 秒后取消 fetch 操作
setTimeout(() => ac.abort(), 1000);

ac.abort() 会向 signal 发送信号,触发它的 abort 事件,并将其 .aborted 属性置为 truefetch() 内部处理会利用这些信息中止掉请求。

上面这个示例演示了如何实现 fetch() 操作的超时处理。如果使用 await 的形式来处理,需要把 setTimeout(...) 放在 fetch(...) 之前:

const ac = new AbortController();
const { signal } = ac;
setTimeout(() => ac.abort(), 1000);
const res = await fetch(url, { signal }).catch(() => undefined); 

为了避免使用 try ... catch ... 来处理请求失败,这里在 fetch() 后加了一个 .catch(...) 在忽略错误的情况。如果发生错误,res 会被赋值为 undefined。实际的业务处理可能需要更合理的 catch() 处理来让 res 包含可识别的错误信息。

本来到这里就可以结束了,但是对每一个 fetch() 调用都写这么长一段代码,会显得很繁琐,不如封装一下:

async function fetchWithTimeout(timeout, resoure, init = {}) {
    const ac = new AbortController();
    const signal = ac.signal;
    setTimeout(() => ac.abort(), timeout);
    return fetch(resoure, { ...init, signal });
}

没问题了吗?不,有问题。

如果我们在上述代码的 setTimeout(...) 里输出一条信息:

setTimeout(() => {
    console.log("It's timeout");
    ac.abort();
}, timeout);

并且在调用的给一个足够的时间:

fetchWithTimeout(5000, url).then(res => console.log("success"));

我们会看到输出 success,并在 5 秒后看到输出 It's timeout

对了,我们虽然为 fetch(...) 处理了超时,但是并没有在 fetch(...) 成功的情况下干掉 timer。作为一个思维缜密的程序员,怎么能够犯这样的错误呢?干掉他!

async function fetchWithTimeout(timeout, resoure, init = {}) {
    const ac = new AbortController();
    const signal = ac.signal;
    const timer = setTimeout(() => {
        console.log("It's timeout");
        return ac.abort();
    }, timeout);
    try {
        return await fetch(resoure, { ...init, signal });
    } finally {
        clearTimeout(timer);
    }
}

完美!但问题还没结束。

万物皆可超时

Axios 和 fetch 都提供了中断异步操作的途径,但对于一个不具备 abort 能力的普通 Promise 来说,该怎么办?

对于这样的 Promise,我只能说,让他去吧,随便他去干到天荒地老 —— 反正我也没办法阻止。但生活总得继续,我不能一直等啊!

这种情况下我们可以把 setTimeout() 封装成一个 Promise,然后使用 Promise.race() 来实现“过时不候”:

race 是竞速的意思,所以 Promise.race() 的行为是不是很好理解?

function waitWithTimeout(promise, timeout, timeoutMessage = "timeout") {
    let timer;
    const timeoutPromise = new Promise((_, reject) => {
        timer = setTimeout(() => reject(timeoutMessage), timeout);
    });
    return Promise.race([timeoutPromise, promise])
        .finally(() => clearTimeout(timer));    // 别忘了清 timer
}

可以写一个 Timeout 来模拟看看效果:

(async () => {
    const business = new Promise(resolve => setTimeout(resolve, 1000 * 10));
    try {
        await waitWithTimeout(business, 1000);
        console.log("[Success]");
    } catch (err) {
        console.log("[Error]", err);    // [Error] timeout
    }
})();

以上就是JavaScript前端超时异步操作完美解决的详细内容,更多关于解决前端超时的异步操作的资料请关注我们其它相关文章!

时间: 2021-11-22

vue axios请求超时的正确处理方法

自从使用Vue2之后,就使用官方推荐的axios的插件来调用API,在使用过程中,如果服务器或者网络不稳定掉包了, 你们该如何处理呢? 下面我给你们分享一下我的经历. 具体原因 最近公司在做一个项目, 服务端数据接口用的是Php输出的API, 有时候在调用的过程中会失败, 在谷歌浏览器里边显示Provisional headers are shown. 按照搜索引擎给出来的解决方案,解决不了我的问题. 最近在研究AOP这个开发编程的概念,axios开发说明里边提到的栏截器(axios.Inter

JavaScript异步操作的几种常见处理方法实例总结

本文实例讲述了JavaScript异步操作的几种常见处理方法.分享给大家供大家参考,具体如下: 引言 js的异步操作,已经是一个老生常谈的话题,关于这个话题的文章随便google一下都可以看到一大堆.那么为什么我还要写这篇东西呢?在最近的工作中,为了编写一套相对比较复杂的插件,需要处理各种各样的异步操作.但是为了体积和兼容性,不打算引入任何的pollyfill,甚至连babel也不允许使用,这也意味着只能以es5的方式去处理.使用回调的方式对于解耦非常不利,于是找了别的方法去处理这个问题.问题是

js异步加载的三种解决方案

默认情况javascript是同步加载的,也就是javascript的加载时阻塞的,后面的元素要等待javascript加载完毕后才能进行再加载,对于一些意义不是很大的javascript,如果放在页头会导致加载很慢的话,是会严重影响用户体验的. (1) defer,只支持IE defer属性的定义和用法(我摘自w3school网站) defer 属性规定是否对脚本执行进行延迟,直到页面加载为止. 有的 javascript 脚本 document.write 方法来创建当前的文档内容,其他脚本

浅谈如何优雅处理JavaScript异步错误

1. try/catch try/catch基本上是大家最常和async/await一起使用的,基本上我们会用它去包围大部分的异步方法.await关键字后面的promise一旦reject了,就会抛出一个异常错误. run(); async function run() { try { await Promise.ject(new Error('Oops!')); } catch (err) { console.error(error.message); } } try/catch同样也可以处理

跟我学习javascript解决异步编程异常方案

一.JavaScript异步编程的两个核心难点 异步I/O.事件驱动使得单线程的JavaScript得以在不阻塞UI的情况下执行网络.文件访问功能,且使之在后端实现了较高的性能.然而异步风格也引来了一些麻烦,其中比较核心的问题是: 1.函数嵌套过深 JavaScript的异步调用基于回调函数,当多个异步事务多级依赖时,回调函数会形成多级的嵌套,代码变成 金字塔型结构.这不仅使得代码变难看难懂,更使得调试.重构的过程充满风险. 2.异常处理 回调嵌套不仅仅是使代码变得杂乱,也使得错误处理更复杂.这

基于javascript的异步编程实例详解

本文实例讲述了基于javascript的异步编程.分享给大家供大家参考,具体如下: 异步函数这个术语有点名不副实,调用一个函数后,程序只在该函数返回后才能继续.JavaScript程序员如果称一个函数为异步的,其意思就是这个函数会导致将来再运行另一个函数,后者取自于事件队列.如果后面这个函数是作为参数传递给前者的,则称其为回调函数. callback 回调函数是异步编程最基本的方式. 采用这种方式,我们把同步操作变成了异步操作,主函数不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟

跟我学习javascript的异步脚本加载

先来看这行代码: <script src = "allMyClientSideCode.js"></script> 这有点儿--不怎么样."这该放在哪儿?"开发人员会奇怪,"靠上点,放到<head>标签里?还是靠下点,放到<body>标签里?"这两种做法都会让富脚本站点的下场很凄惨.<head>标签里的大脚本会滞压所有页面渲染工作,使得用户在脚本加载完毕之前一直处于"白屏死机&

Javascript实现异步编程的过程

相信不少人对Javascript单线程表示怀疑:为何单线程可以实现异步操作呢?其实Javascript确实是单线程的(我们不妨把这个线程称作主线程),但它实现异步操作的方式确实借助了浏览器的其他线程的帮助.那其他线程是怎么帮助Javascript主线程来实现异步的呢?答案就是任务队列(task queue)和事件循环(event loop). 任务队列 首先,作为单线程语言,在Javascript中定义的任务都会在主线程中执行.但是并不是每个任务都会立刻执行,而这种不立刻执行的任务我们称作异步任

Javascript中的异步编程规范Promises/A详细介绍

Javascript里异步编程逐渐被大家接受,先前大家一般通过回调嵌套,setTimeout.setInterval等方式实现,代码看起来非常不直观,不看整个代码逻辑很难快速理解.Javascript里异步函数大概有I/O函数(Ajax.postMessage.img load.script load等).计时函数(setTimeout.setInterval)等. 这些我们都很熟悉,在复杂的应用中往往会嵌套多层,甚至以为某些步骤未完成而导致程序异常,最简单的例子:比如你往DOM中注入节点,你必

详谈nodejs异步编程

目前需求中涉及到大量的异步操作,实际的页面越来越倾向于单页面应用.以后可以会使用backbone.angular.knockout等框架,但是关于异步编程的问题是首先需要面对的问题.随着node的兴起,异步编程成为一个非常热的话题.经过一段时间的学习和实践,对异步编程的一些细节进行总结. 1.异步编程的分类 解决异步问题方法大致包括:直接回调.pub/sub模式(事件模式).异步库控制库(例如async.when).promise.Generator等. 1.1 回调函数 回调函数是常用的解决异

新手如何快速理解js异步编程

前言 异步编程从早期的 callback.事件发布\订阅模式到 ES6 的 Promise.Generator 在到 ES2017 中 async,看似风格迥异,但是还是有一条暗线将它们串联在一起的,就是希望将异步编程的代码表达尽量地贴合自然语言的线性思维. 以这条暗线将上述几种解决方案连在一起,就可以更好地理解异步编程的原理.魅力. ├── 事件发布\订阅模式 <= Callback ├── Promise <= 事件发布\订阅模式 ├── Async.Await <= Promise

C#实现异步编程的方法

最近在我参与的几个.Net项目中都有用到异步编程,作为一名.Net小白,很有必要好好地学习一下C#异步编程. 什么是异步 异步指的就是不用阻塞当前线程来等待任务的完成,而是将任务扔到线程池中去执行,当前线程可以继续向下执行,直至其它线程将任务完成,并回调通知当前线程.整个任务从开始到结束都是异步完成的,不会阻塞当前线程.因此,异步很重要的一点就是,不会阻塞当前线程. 实现异步编程 在C#语言中,主要是通过委托来实现异步编程的,在委托类型中定义了两个方法BeginInvoke()和EndInvok

javascript异步编程代码书写规范Promise学习笔记

最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下. 一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编程时候代码书写的方式产生的. 随着javascript的发展,异步的场景越来越多.前端有AJAX,setTimeout等,后端Node异步更多.按照传统的做法,那么就是各种回调嵌回调.代码可以把人绕晕. 这个时候,CommonJS社区提出了一个叫做Promise/A+的规范,这个规范定义了如何书写异步代

详谈javascript异步编程

异步编程带来的问题在客户端Javascript中并不明显,但随着服务器端Javascript越来越广的被使用,大量的异步IO操作使得该问题变得明显.许多不同的方法都可以解决这个问题,本文讨论了一些方法,但并不深入.大家需要根据自己的情况选择一个适于自己的方法. 本文为大家详细介绍js中的异步编程,具体内容如下 一 关于事件的异步 事件是JavaScript中最重要的一个特征,nodejs就是利用js这一异步而设计出来的.所以这里讲一下事件机制. 在一个js文件中,如果要运行某一个函数,有2中手段