我们有时候需要遍历数组的元素,将它们传入到异步函数中执行,其中的异步写法容易写错,我们来看一下有哪些易错点。
假设我们有个异步方法 sleepPromise,形式如下:
这里为了方便演示,使用 setTimeout 写成了个 promise 形式的 sleep 方法。传入的 t 为延迟执行的时间,msg 为信息内容。
在实际开发中,异步方法可能是传入用户好友 id 查找数据库,获得简单的好友信息。
假设我们需要在下面代码的注释位置下方写一个异步便利实现。
通常前端一看到要遍历数组,就会用 forEach。如果你不够老道,可能会写出如下的实现:
输出结果为;
这种写法并不对,其实是将遍历写成了同步。
问题出在哪?出在 forEach 本身并不支持异步写法,你在 forEach 方法的前面加不加 await 关键字都是无效的,因为它的内部没有处理异步的逻辑。
forEach 是 ES5 的 API,要比 ES6 的 Promise 要早的多得多。为了向后兼容,forEach 以后也不会支持异步处理。
所以 forEach 的执行并不会阻塞 loopAsync 之后的代码,所以会导致阻塞失败,先输出 [end]。
使用普通的 for 循环写法,await 的外层函数就仍就是 loopAysnc 方法,就能正确保存阻塞代码。
但这里的问题是,这些异步方法的执行是 串行 的。可以看到总共执行了 6 s。
如果我们的这些请求是有顺序的依赖关系的,这样写是没问题。
但如果我们的场景是根据用户 id 数组从数据库中查找对应用户名,我们的时间复杂度就是 O(n) ,是不合理的。
此时我们需要改写为 并行 的异步,并且还要保证所有异步都执行完后才执行下一步。我们可以用 Promise.all()。
首先,我们需要根据 tasks 数组生成对应的 promise 对象数组,然后传入到 Promise.all 方法中执行。
这样,这些异步方法就会同时执行。当所有异步都执行完毕后,代码才往下执行。
输出结果如下:
3 秒就完事了,太强了。
前面说到 forEach 底层并没有实现异步的处理,才导致阻塞失效,那么我们其实不妨实现支持异步的简易 forEach。
并行实现:
串行实现:
用法:
简单总结一下。
一般来说,我们更常用 Promise.all 的并行执行异步的方法,常见于数据库查找一些 id 对应的数据的场景。
for 循环的串行写法适用于多个异步有依赖的情况,比如找最终推荐人。
forEach 则是纯粹的错误写法,除非是不需要使用 async/await 的情况。
javascript语言是单线程机制。所谓单线程就是按次序执行,执行完一个任务再执行下一个。对于浏览器来说,也就是无法在渲染页面的同时执行代码。
单线程机制的优点在于实现起来较为简单,运行环境相对简单。缺点在于,如果中间有任务需要响应时间过长,经常会导致
页面加载错误或者浏览器无响应的状况。这就是所谓的“同步模式”,程序执行顺序与任务排列顺序一致。对于浏览器来说,
同步模式效率较低,耗时长的任务都应该使用异步模式;而在服务器端,异步模式则是唯一的模式,如果采用同步模式个人认为
服务器很快就会出现12306在高峰期的表现。。。。
异步模式的四种方式:
1.回调函数callback
所谓回调函数,就是将函数作为参数传到需要回调的函数内部再执行。
典型的例子就是发送ajax请求。例如:
$.ajax({
async: false,
cache: false,
dataType: 'json',
url: "url",
success: function(data) {
console.log('success')
},
error: function(data) {
console.log('error')
}
})
当发送ajax请求后,等待回应的过程不会堵塞程序运行,耗时的操作相当于延后执行。
回调函数的优点在于简单,容易理解,但是可读性较差,耦合度较高,不易于维护。
2.事件驱动
javascript可以称之为是基于对象的语言,而基于对象的基本特征就是事件驱动(Event-Driven)。
事件驱动,指的是由鼠标和热键的动作引发的一连串的程序操作。
例如,为页面上的某个
$('#btn').onclick(function(){
console.log('click button')
})
绑定事件相当于在元素上进行监听,是否执行注册的事件代码取决于事件是否发生。
优点在于容易理解,一个元素上可以绑定多个事件,有利于实现模块化;但是缺点在于称为事件驱动的模型后,流程不清晰。
3.发布/订阅
发布订阅模式(publish-subscribe pattern)又称为观察者模式(Observer pattern)。
该模式中,有两类对象:观察者和目标对象。目标对象中存在着一份观察者的列表,当目标对象
的状态发生改变时,主动通知观察者,从而建立一种发布/订阅的关系。
jquery有相关的插件,在这不是重点不细说了。。。。回头写个实现贴上来
4.promise模式
promise对象是CommonJS工作组提供的一种规范,用于异步编程的统一接口。
promise对象通常实现一种then的方法,用来在注册状态发生改变时作为对应的回调函数。
promise模式在任何时刻都处于以下三种状态之一:未完成(unfulfilled)、已完成(resolved)和拒绝(rejected)。以CommonJS
Promise/A
标准为例,promise对象上的then方法负责添加针对已完成和拒绝状态下的处理函数。then方法会返回另一个promise对象,以便于形成promise管道,这种返回promise对象的方式能够支持开发人员把异步操作串联起来,如then(resolvedHandler,
rejectedHandler)。resolvedHandler
回调函数在promise对象进入完成状态时会触发,并传递结果;rejectedHandler函数会在拒绝状态下调用。
Jquery在1.5的版本中引入了一个新的概念叫Deferred,就是CommonJS promise A标准的一种衍生。可以在jQuery中创建
$.Deferref的对象。同时也对发送ajax请求以及数据类型有了新的修改,参考JQuery API。
除了以上四种,javascript中还可以利用各种函数模拟异步方式,更有诡异的诸如用同步调用异步的case
只能用team里同事形容java和javascript的一句话作为结尾:
“写java像在高速路上开车,写javascript像在草原上开车”-------------以此来形容javascript这种无类型的语言有多自由
but,如果草原上都是坑。