Open thzt opened 6 years ago
嗯,不错的异步问题,要看具体业务的要求,有时候是需要每次点击都要执行异步操作。
有时候业务要求,必须等待上一次操作完成后,才能执行下一次操作。
异步问题是核心问题
图文并茂很棒 👍
利用队列和变量锁来使得每个异步任务依次执行,很赞的思路。其实根据需求,如果只是需要按点击顺序展示 ,那么从性能上考虑,并发请求,结果排序是性能更好的。但是如果各个异步操作之间存在依赖关系,下个异步操作依赖于上个异步操作。那么这个方法真的非常好。
利用队列和变量锁来使得每个异步任务依次执行,很赞的思路。其实根据需求,如果只是需要按点击顺序展示 ,那么从性能上考虑,并发请求,结果排序是性能更好的。但是如果各个异步操作之间存在依赖关系,下个异步操作依赖于上个异步操作。那么这个方法真的非常好。
并发请求的话excue那里稍微改造一下就能用,
while(this.queues.length){
// single request
const task = this.queues.shift()
const result = await task()
this.results.push(result)
this._callback && this._callback(value);
// concurrent request
const batchTasks = this.queues.splice(0, this.REQUEST_SIZE)
const results = await Promise.all(batchTasks.map(t => t()))
this.results.push(result)
}
`
这是来自QQ邮箱的假期自动回复邮件。 您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
1. 异步任务
我从具体的项目中分离出了一个有趣的问题,可以描述如下:
页面上有一个按钮,每次点击它,都会发送一个ajax请求, 并且,用户可以在ajax返回之前点击它。
现在我们要实现一个功能, 以按钮的点击顺序展示ajax的响应结果。
2. 准备活动
为了以后编码的方便,先将ajax请求mock一下,
然后,假设按钮的
id
为sendAjax
,3. 冷静再冷静
一开始,我们可能会想到这样的办法。 可惜,这是有问题的。
因为
click
事件,可能会在后面async函数还未返回之前,再次触发。 导致前一个请求还未返回,后面又发起了新请求。其次,我们可能还会想到,记录每一个请求的时间戳,将结果排序, 这也是有问题的,因为我们不知道未来还有多少次点击(<- 下文的关键信息), 如果无法拿到所有的结果,那么排序就有困难了。
那怎么办呢? 如果请求还未返回之前,能进行控制就好了。
4. 让我们Lazy一点
于是我想到了把新请求lazy化,放到一个队列中, 如果当前有其他任务在执行,就暂不处理。 否则,如果当前是空闲的,那就把队列中的任务都取出来,依次执行。
以上代码,我用了一个队列和变量锁,对新请求进行了管控。
其中的关键点是
execute
的异步性, 我们看到add
函数在尾部调用了this.execute();
,会立即返回。 这样就不会阻塞JavaScript线程,可以多次调用add
函数了。下面我们来看下它的使用方法吧,
5. 更远一些
上文中有一句话,启发了我, 迫使我从不同的角度重新考虑了这个问题。
我们提到,由于“我们不知道未来还有多少次点击”,所以是无法进行排序的。 因此,我发现这是一个和“无穷流”相关的问题。 即,我们不应该把事件看成回调,而是应该看成流(stream)。
所以,我们可以寻找响应式的方式来解决它。 以下两篇文章可以帮你快速回顾一下响应式编程(Reactive Programming)。 ——也称反应式编程 _(:зゝ∠)_
你所不知道的响应式编程 函数响应式流库探秘
好了,下面我们要开始进行响应式编程了。 首先,
click
事件可以形成一个“点击流”,这里的
cont
指的是Continuation,可以参考上面提到的第二篇文章。其次,我们需要将这个“点击流”,变换成最终的“ajax结果流”, 并且保证“ajax结果流”的顺序,与“点击流”的顺序相同。
因此,问题在概念上就被简化了, 事实上,所有的
stream
连同operator
一起,构成了一个Monad
。下面我们来编写
operator
吧,用来对流进行变换,我们只要记着, “什么时候调用cont
就什么时候把东西放到结果流中”,即可。我们再来看下怎么使用它,是不是更加通俗易懂了呀。