Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

不忍直视的逻辑深坑

  我经常会搞出一坨复杂的逻辑来解决某些业务上的问题,然而这些看似「无懈可击」的逻辑发到生产环境后总是会出现一些奇奇怪怪的问题。特别是在强交互和大量异步逻辑存在的场景下,感觉自己的智商完全不够用。这篇文章就举个「表单提交」作为逻辑深坑的例子。
  传统页面的表单提交很简单,只要放置一个 FORM 元素,在 FORM 上通过 action 属性设置要提交的目标 URL;通过 method 属性设置提交的方法。然后再放一个 submit 控件上去,这样前端的事情就做完了。
  但如果是现代 Web,使用 FORM 元素提交表单带来的页面刷新肯定是不能接受的。于是我们可能会劫持 FORM 的 submit 事件,然后把 FORM 的提交转换成程序提交。具体的做法可能是在 FORM 元素的 submit 事件上阻止默认行为,然后收集表单数据,最后创建一个 XHR 对象把数据提交到服务器。
  流程虽然简单,但是如果只是做这些事情肯定是会遇到坑的。一个典型的坑就是重复提交。用户可能多次点击提交按钮,如果浏览器没有限制的话就会发出多个提交请求。所以为了防止多次请求,我们可能在用户点击提交按钮时将按钮置为不可用,直到请求结束。然而这里还有个坑,注意是「请求结束」而不是「请求成功」。即使请求失败了,我们也应该恢复提交按钮的状态。
  到这里已经是「无懈可击」了么?并没有!因为 XHR 对象默认没有超时,请求可能因为 DNS 服务器丢包之类的问题而卡住几分钟,所以根本不会达到「请求结束」的状态。也就是说,在渣网环境,按钮依然可能在被点击后锁死。这是可能还需要考虑设置一个超时,比如请求如果 5 秒钟没有响应就终止掉。
  加上超时就 OK 了么?那就太天真了。我们终止一个超时的请求只是客户端终止而已,客户端并不知道服务器端到底有没有处理这个请求。如果我们贸然恢复按钮状态,也许上一个请求服务器已经处理,这是用户再次点击按钮依然会造成重复提交。所以在客户端因为超时而终止请求时,我们可能还要向服务器端验证先前的请求是否成功受理。如果先前的请求已受理就直接执行成功的逻辑,只有当确认先前是失败时才恢复按钮状态。
  这样真的就「无懈可击」了么?其实也还没有,因为提交可能延迟,确认成败的请求可能先完成,然后被终止的提交才进行。于是同样可能出现诡异的情况。但我不想再纠结更深层的东西,因为维护这些逻辑的成本太高(其实是因为我的智商有限),如果业务代码总是考虑这样的细节肯定会变成一坨。
  适当维护逻辑就好了,一个普通的表单提交只要做到最基本的按钮不被重复点击即可,从超时往后的考虑都可以无视。如果这个表单确实非常关键,那么可能需要以幂等化的形式来实现,比如在提交前先向服务器申请一个事务 ID,之后即使重复提交,服务器认为是同一个事务也不会重复处理。当然这也是不完美的,虽然看似「无懈可击」,但仔细想想还是有漏洞的,这里就不扯了。
  以上这一坨废话其实只是个逻辑深坑的典型,还有动画、交互,一大堆东西都存在同样的坑。所以写程序只是把基本功能写出来是非常容易的,也许一个表单几行代码就能搞定。要是考虑上各种问题的话可能就是一个框架级的话题了。
网名:
34.203.245.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^