一、前言
作为一个新人小白来说,JS的异步处理让我很头疼。。
那么什么是异步,为什么产生,又怎么处理呢?且看下文
单线程
作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
单线程最大的好处就是实现简单,执行环境单纯
但是也有一个很致命的缺点:当一个线程需要执行很长时间,那么后续线程就需要一直等着,如果这个线程陷入了死循环,那么后续线程都将得不到执行的机会,页面卡死。
所以为了避免这个问题,JS制定了两种执行模式,同步模式和异步模式
异步与同步
同步模式 就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行的调入和完成顺序与任务的排列顺序是一致的、同步的;
异步模式 则完全不同,当一个任务执行时,若它执行时间很短,则它执行完后,后面任务紧跟着它执行,若它涉及异步操作,则将此任务调入主线程队列,异步操作给相应的异步操作理模块执行,与此同时调取下一个任务执行,等到异步模块处理完异步操作后,给主线程一个成功或失败信号,再将异步操作结果调入主线程队列排队,直到执行完。(具体流程参见js事件轮询机制 -_-||)
所以在我理解,异步的关键就是如何实现在异步完成后返回这个信号,来告诉主线程可以进行异步操作结果的处理。
二、异步实现方式
1. 回调函数
回调函数 就是将A函数作为B函数的参数传递给B,等B函数执行完成后再执行
比如我想实现B函数实现一系列复杂运算(或异步操作),实现完成后改变dom,而A函数需要在B函数更新完dom后,再对dom执行一些操作
用回调实现:
var domList = [];
function B(dom,callback){
setTimeout(()=>{
dom.push(1);
callback();
},2000);
}
function A(){
console.log(domList);
}
B(domList,A)
这里用延迟执行来代替异步操作,上面结果在两秒后打印 [1],在B中的callback调用这里,就是手动模拟了异步操作完成的信号,告诉A可以进行后续操作了。
注意:处理异步可以用回调,但是回调不一定就是异步
2. 事件监听
采取事件驱动模式,即定义一个事件,当这个事件发生时,执行一些处理。
在异步中的实现即 先定义事件发生时的处理函数并且监听这个事件,当进入异步且执行完后触发事件,进而触发监听这个事件相关联的处理函数。
监听的函数有:on、bind、listen、addEventaddEventListener、observe
还是上述功能,用事件监听实现:
var domList = [];
function A(){
console.log(domList);
}
// 绑定监听 当B发生updateComplate事件时,执行A函数
B.on('updateComplate',A);
function B(){
setTimeout(()=>{
domList.push(1);
B.trigger('updateComplate');
},2000);
}
A();
这种方式容易理解,也可以为一个事件监听绑定多个事件处理函数
但是程序会变成事件驱动型的,运行流程不清晰
3. 发布/订阅
发布者发出通知 =>主题对象收到通知并推送给订阅者 => 订阅者执行相应的操作。
// 一个发布者 publisher,功能就是负责发布消息 - publish
var pub = {
publish: function () {
dep.notify();
}
}
// 多个订阅者 subscribers, 在发布者发布消息之后执行函数
var sub1 = {
update: function () {
console.log(1);
}
}
var sub2 = {
update: function () {
console.log(2);
}
}
var sub3 = {
update: function () {
console.log(3);
}
}
// 一个主题对象
function Dep() {
this.subs = [sub1, sub2, sub3];
}
Dep.prototype.notify = function () {
this.subs.forEach(function (sub) {
sub.update();
});
}
// 发布者发布消息, 主题对象执行notify方法,进而触发订阅者执行Update方法
var dep = new Dep();
pub.publish();
4. promise对象
promise对象用于处理异步操作
它代表一个未完成、但是预计将来会完成的操作
三种状态:
- pending:初始值,不是resolved,也不是rejected
- resolved:操作成功
- rejected:操作失败
- 由pending转换为resolved
- 由pending转换为rejected
对上述功能的实现:
var domList = [];
function B(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
domList.push(1);
resolve(domList);
},2000);
});
}
function A(){
console.log(domList);
}
B().then((result)=>{
A();
}).catch(err=>{
console.log(err)
});
promise中,resolve和reject相当于异步操作完成的触发信号,then相当于resolve后的成功处理函数,catch相当于reject后的失败处理函数
promise的then可以链式调用
var domList = [];
function B(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
domList.push(1);
resolve(domList);
},2000);
});
}
function A(){
console.log(domList);
domList.push(2);
}
B().then((result)=>{
A();
}).then((result)=>{
A();
}).catch(err=>{
console.log(err)
});
5. async/await
初识async/await是在实际代码中,是怎么写的呢,async定义一个函数,在里面 使用await 可以让后面的代码等待此行执行完后再执行,我去,这我一理解,那我需要耗时的函数定义为async ,实际耗时操作加一个await,那不是分分钟变异步为同步了吗(-_-'')
于是年轻的我开始写测试了
async function test(){
console.log(1);
await setTimeout(()=>{
console.log(2);
},2000);
console.log(3);
}
test();
test执行应该是 1 ,两秒后 2,3 了呗,结果呢。。。。 1,3,两秒后 2,控制台还多了个什么Promise
好吧,还是安安静静回去看教程吧!!
使用方法:
- async放于function定义之前,await写在async函数中,单独放在外层是不起作用的
- async/await基于promise实现,被定义的异步函数执行后返回promise(这就能解释为啥多打印出来个promise对象了),若成功,使用.then()来获取结果,不成功呢,用.catch()获取失败结果
- await等待的后续结果必须返回一个promise,一般的函数调用不管用(。。。好吧,基于promise实现的,证明人家认识promise,愿意等promise,不认识的还不乐意等了,这就是为啥我等延时执行不管用)
function asyncF(){
return new Promise((resove,reject)=>{
setTimeout(()=>{
resove('2');
},2000);
})
}
async function callAsync(){
console.log('1')
await asyncF().then(result=>{
console.log(result);
});
console.log('3');
}
callAsync();
这样就可以执行了
async/await基于promise实现的,那他比promise有哪些优势呢?(毕竟新整的东西还不如以前的还不如不整)
- async/await更加语义化,async 是“异步”的简写,async function 用于申明一个 function 是异步的; await,可以认为是async wait的简写, 用于等待一个异步方法执行完成;
- async/await是一个用同步思维解决异步问题的方案(等结果出来之后,代码才会继续往下执行)
- 可以通过多层 async function 的同步写法代替传统的callback嵌套
function asyncF(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2*x);
}, 2000);
})
}
async function callAsync() {
let r1 = await asyncF(1);
let r2 = await asyncF(r1);
let r3 = await asyncF(r2);
console.log(r3)
}
callAsync();
类似同步的异步使用,以及async函数对promise对象的自动转换操作简便