JavaScript 异步调用框架 (Part 1 - 问题 & 场景)

问题
在Ajax应用中,调用XMLHttpRequest是很常见的情况。特别是以客户端为中心的Ajax应用,各种需要从服务器端获取数据的操作都通过XHR异步调用完成。然而在单线程的JavaScript编程中,XHR异步调用的代码风格实在是与一般的JavaScript代码格格不入。

额外参数
考虑一个除法函数,如果它是纯客户端的同步函数,那么签名会是这样的:

function divide(operand1, operand2)

然而假设我们对客户端除法的精度不满意,于是把除法转移到服务器端来执行,那么它是个需要调用XHR的异步函数,签名也就可能会是以下几种之一:


代码如下:

function divide(operand1, operand2, callback)
function divide(operand1, operand2, successCallback, failureCallback)
function divide(operand1, operand2, options)

我们必须在签名中引入新的参数来传递回调函数,不能选择让函数变成阻塞式的同步调用。

可传递性
不仅仅直接操作XHR的函数需要引入新的参数,这种复杂性还会顺着调用栈向外传递。例如说,我们对加减乘除四则运算作了封装,只向外暴露一个运算接口:

function calculate(operand1, operand2, operator)

这个calculate函数根据operator参数来调用内部的plus, subtract, multiply, divide函数。然而,因为divide函数变成了异步函数,所以整个calculate函数不得不也转变为异步函数:

function calculate(operand1, operand2, operator, callback)

同时,在调用栈之上凡是需要调用到calculate的函数,都必须变成异步的,除非它并不需要向上一级调用函数返回结果。

同步并存
尽管calculate函数变成了一个异步函数,它所调用的plus, subtract, multiply函数还是同步函数,只有调用divide时是异步的,这时候calculate就是一个异步同步并存函数。

这会带来什么问题?calculate的调用者看到函数签名自然会认为calculate是个异步函数,因为它需要传递回调函数,然而calculate的执行方式却是不确定的。考虑如下调用:

calculate(operand1, operand2, operator, callback);
next();

这里涉及到callback和next两个函数,它们哪个先执行哪个后执行是不确定的,或者说是依赖于calculate具体实现的。

如果calculate的实现是,当不需要进行异步操作时,直接调用callback。那么,在执行加减乘法时callback会在next之前被调用;在执行除法时next会在callback之前调用。

如果我们不喜欢这种不确定性,我们可以改变一下calculate的实现,把同步调用也都改为setTimeout形式的,这样在任何情况下next都一定会在callback之前被调用。

然而后面一种做法依赖于成本较高的实现方式,开发者一个不小心(或者摆明偷懒)就会漏掉setTimeout,导致函数调用顺序变得不确定,所以我们会希望这是框架帮助实现的功能,在使用框架时无法把这绕过。

场景
在这里,我举一个关于上述问题的具体应用场景。(为简化问题,描述已略作修改,与实际应用并不一致。)

在百度Hi网页版里面,我们会在客户端保存一个用户对象列表,在打开和这个用户的聊天窗口时,我们需要从中读取这个用户的信息。这个操作就涉及很多可能同步又可能异步的分支:

用户对象未缓存
异步读取用户信息
用户对象已缓存
用户是好友(信息更新会由服务器端推送)
同步读取用户信息
用户不是好友(信息更新需要由客户端拉取)
可以接受缓存信息
同步读取用户信息
必须获取最新信息
异步读取用户信息
可以看到,分支的结果最终既有同步的,也有异步的。并且这些分支还不是在一个函数里完成,而是在几个函数里实现。也就是说,按照传统的模式,这些函数一部分是同步的,一部分是异步的,由于异步的传递性,最终调用栈顶层的函数都是异步的。

为了解决这个问题,我们需要写一个异步调用框架,用一种统一的方式来进行调用,把同步和异步调用都合并为一种返回方式。

(0)

相关推荐

  • JavaScript异步调用定时方法并停止该方法实现代码

    复制代码 代码如下: (function() { var t = window.setInterval(function() { $('#ajaxGame').load('RefreshCurrentPlayerServlet #ajaxGame', function() { youWin(); youlose(); }); }, 10000); function youWin() { if ($('#status:contains("YOU ARE THE WINNER!")').l

  • javascript 支持链式调用的异步调用框架Async.Operation

    复制代码 代码如下: Async = {}; Async.Operation = function(options) { options = options || {}; var callbackQueue = []; var chain = (options.chain && options.chain === true) ? true : false; var started = false; var innerChain = null; this.result = undefined

  • JavaScript 异步调用框架 (Part 5 - 链式实现)

    调用入口 链式调用存在Async.go方法和Async.chain方法两个入口,这两个入口本质上是一致的,只是Async.chain方法在调用时先不提供初始参数,而Async.go方法在调用时提供了初始参数并启动异步调用链. 复制代码 代码如下: Async.chain = function() { var chain = new Async.Operation({ chain: true }); return chain; }; Async.go = function(initialArgum

  • JavaScript 异步调用框架 (Part 6 - 实例 & 模式)

    封装Ajax 设计Async.Operation的最初目的就是解决Ajax调用需要传递callback参数的问题,为此我们先把Ajax请求封装为Async.Operation.我在这里使用的是jQuery,当然无论你用什么基础库,在使用Async.Operation时都可以做这种简单的封装. 复制代码 代码如下: var Ajax = {}; Ajax.get = function(url, data) { var operation = new Async.Operation(); $.get

  • JavaScript 异步调用

    问题 可修改下面的 aa() 函数,目的是在一抄后用 console.log() 输出 want-value function aa() { setTimeout(function() { return "want-value"; }, 1000); } 但是,有额外要求: aa() 函数可以随意修改,但是不能有 console.log() 执行 console.log() 语句里不能有 setTimeout 包裹 解答 也许这是个面试题,管它呢.问题的主要目的是考察对异步调用执行结果

  • Android与JS之间跨平台异步调用实例详解

    Android与JS之间跨平台异步调用 为什么突然要搞这个问题呢? 在开发浏览器的时候遇到这个狗血的问题,花了将近1天的时间才想到这个解决方案,Android与JavaScirpt互调. 因为接口是抓取的别人的,所以出现了JS跨域问题,Android闪亮登场搞定了. GIF动画演示   WebView相关设置 WebSettings mWebSettings = getSettings(); mWebSettings.setDefaultTextEncodingName("UTF-8"

  • JavaScript 异步调用框架 (Part 1 - 问题 & 场景)

    问题 在Ajax应用中,调用XMLHttpRequest是很常见的情况.特别是以客户端为中心的Ajax应用,各种需要从服务器端获取数据的操作都通过XHR异步调用完成.然而在单线程的JavaScript编程中,XHR异步调用的代码风格实在是与一般的JavaScript代码格格不入. 额外参数 考虑一个除法函数,如果它是纯客户端的同步函数,那么签名会是这样的: function divide(operand1, operand2) 然而假设我们对客户端除法的精度不满意,于是把除法转移到服务器端来执行

  • JavaScript 异步调用框架 (Part 3 - 代码实现)

    类结构 首先我们来搭一个架子,把需要用到的似有变量都列出来.我们需要一个数组,来保存回调函数列表:需要一个标志位,来表示异步操作是否已完成:还可以学IAsyncResult,加一个state,允许异步操作的实现者对外暴露自定义的执行状态:最后加一个变量保存异步操作结果. 复制代码 代码如下: Async = { Operation: { var callbackQueue = []; this.result = undefined; this.state = "waiting"; th

  • JavaScript 异步调用框架 (Part 4 - 链式调用)

    现实开发中,要按顺序执行一系列的同步异步操作又是很常见的.还是用百度Hi网页版中的例子,我们先要异步获取联系人列表,然后再异步获取每一个联系人的具体信息,而且后者是分页获取的,每次请求发送10个联系人的名称然后取回对应的具体信息.这就是多个需要顺序执行的异步请求. 为此,我们需要设计一种新的操作方式来优化代码可读性,让顺序异步操作代码看起来和传统的顺序同步操作代码一样优雅. 传统做法 大多数程序员都能够很好的理解顺序执行的代码,例如这样子的: 复制代码 代码如下: var firstResult

  • JavaScript 异步调用框架 (Part 2 - 用例设计)

    传递回调 我们首先要考虑的一个问题是,如何传递回调入口.在最传统的XHR调用当中,回调函数会被作为最后一个参数传递给异步函数: 复制代码 代码如下: function asyncOperation(argument, callback) 在参数相当多的时候,我们可以把参数放到一个JSON里面,这样参数就如同具名参数一样,可以通过参数名选择性的传递参数,不传递的参数相当于使用默认值.这是从Prototype开始就流行起来的做法: 复制代码 代码如下: function asyncOperation

  • SpringBoot异步调用方法实现场景代码实例

    一.背景 项目中肯定会遇到异步调用其他方法的场景,比如有个计算过程,需要计算很多个指标的值,但是每个指标计算的效率快慢不同,如果采用同步执行的方式,运行这一个过程的时间是计算所有指标的时间之和.比如: 方法A:计算指标x,指标y,指标z的值,其中计算指标x需要1s,计算指标y需要2s,指标z需要3s.最终执行完方法A就是5s. 现在用异步的方式优化一下 方法A异步调用方法B,方法C,方法D,方法B,方法C,方法D分别计算指标x,指标y,指标z的值,那么最终执行完方法A的时间则是3s. 还有一种用

随机推荐