杂谈:异步解决方案

128 阅读4分钟

高频面试题:异步解决方案有哪些?

我们知道,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的执行模式可以分为同步执行异步执行,异步执行又可以分为,异步执行又分为无需等待需要等待两种业务场景。