高频面试题:异步解决方案有哪些?
我们知道,JavaScript(简称js)是单线程的语言。单线程,指的是在执行的过程中一个时间只能执行一个任务,后面的任务得等待执行。如果出现耗时比较长的任务,后续的任务都会被阻塞。也有可能这个耗时任务中在当前队列中我们只需要其执行结果,比如,复杂的数据计算,ajax请求后台数据等。这个时候,异步执行就应时而生。
1、回调函数
假如我们有同步任务1,需要执行1s的异步任务和同步任务2。执行过程中,如果等待异步任务执行结束去执行同步任务2的话,同步任务2将会被阻塞1s,如果当前同步任务2不依赖于异步任务的话,我们完全可以先执行同步任务,再执行异步任务,解决方案如下。
function ajaxFun(cb) {
setTimeout(() => {
// 模拟1s后获取到数据,并将数据返回给回调函数
cb(100);
}, 1000);
};
function main() {
console.log('同步任务1');
ajaxFun(function (data) {
// 回调函数获取到了异步请求的数据
console.log('获取到的异步数据是:', data);
});
console.log('同步任务2');
}
main();
2、发布订阅者模式
发布订阅者模式是应用频率非常高的设计模式之一,简单表述就是,定义一个对象集中管理消息(事件),有订阅者订阅消息,发布者发布消息,集中管理的消息列表是发布者和订阅者之间的桥梁,解决方案如下:
// 定义发布订阅者事件管理中心
var eventsManage = {
events: [],
notify() {
this.events.forEach((event) => {
event();
})
},
addSub() {
this.events.push(fn)
}
}
// 先添加异步事件
eventsManage.events.push(() => {
console.log('异步事件1')
})
eventsManage.events.push(() => {
console.log('异步事件2')
})
// 过了一些时间调起异步事件
setTimeout(() => {
eventsManage.notify();
}, 1000)
3、Promise
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理和更强大,then方法的出现让异步可以链式书写。举例如下:
const promise = new Promise(function (resolve) {
setTimeout(() => {
resolve(100)
}, 500);
});
promise.then(function (val) {
console.log('异步事件执行结果1', val);
return new Promise(function (resolve) {
setTimeout(() => {
resolve(200)
}, 1000)
})
}).then(function (val) {
console.log('异步事件执行结果2', val)
})
4、generator
Generator 函数是 ES6 提供的一种异步编程解决方案,书写特点是function关键字与函数名之间有一个星号,并且函数体内部使用yield表达式,定义不同的内部状态。执行 Generator 函数会返回一个遍历器对象,可以通过.next()的方式依次执行每一个内部状态。举例如下:
// 定义generator函数
function* async () {
yield console.log('异步事件1');
yield console.log('异步事件2');
}
// 生成缓存函数
var generator = async();
// 过了1s调起异步事件1
setTimeout(() => {
generator.next();
}, 1000)
// 过了1s调起异步事件2
setTimeout(() => {
generator.next();
}, 2000)
5、async
ES2017 标准引入了 async 函数,使得异步操作变得更加方便,它就是 Generator 函数的语法糖。书写特点是在函数定义前添加async,在函数内部通过await的方式等待异步函数返回执行结果,再去执行后续的逻辑。
// 定义异步执行函数
function p1 () {
return new Promise(resolve => {
setTimeout(() => {
resolve('异步函数1成功回调结果');
}, 500);
})
}
function p2 () {
return new Promise(resolve => {
setTimeout(() => {
resolve('异步函数2成功回调结果');
}, 1000);
})
}
// 定义async函数
async function execute() {
var res1 = await p1();
console.log(res1);
var res2 = await p2();
console.log(res2);
}
execute();
6、递归函数
有一种需要顺序执行异步函数队列的场景,也就是说下一个异步的执行必须依赖于上一个异步的执行结果,而且,这样的异步函数不止两三个,那么,通过递归的方式将是异步执行的解决方案,递归的终止条件就是异步队列的长度。举例如下:
// 定义异步事件队列
var events = []
var fn1 = function () {
return new Promise(resolve => {
setTimeout(() => {
resolve('异步执行函数1');
}, 1000)
})
}
var fn2 = function () {
return new Promise(resolve => {
setTimeout(() => {
resolve('异步执行函数2');
}, 500);
})
}
events.push(fn1, fn2)
// 定义异步队列顺序执行函数
function execute(events) {
var index = 0
var total = events.length
var next = () => {
const event = events[index];
event().then((val) => {
console.log(val);
index++
if (index < total) {
next()
}
})
}
next()
}
execute(events)
总结
JavaScript的执行模式可以分为同步执行和异步执行,异步执行又可以分为,异步执行又分为无需等待和需要等待两种业务场景。