欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JavaScript异步函数同步方法

程序员文章站 2024-02-22 12:55:10
...

首先这里非常感谢慕课网蜗牛老湿,本文内容是基于老师讲解koa2实现原理的时候整理出来的,好吧,这里直接进入主题。

在我们平时进行前端开发的时候,经常需要用到异步函数,最常见的是发送网络请求,在发送网络请求之后,在回调函数中对请求结果再进行下一步操作,下面来模拟这个操作步骤:

/*
*   模拟网络请求的函数
*/
function request(callback) {
    let time = Math.random() * 1000                 //为了模拟每次请求的时长不同,这里每次等待随机时间01s
    setTimeout(() => callback(), time)              //请求结束之后,调用回调函数
}

request(function () {
    console.log(1)
})
request(function () {
    console.log(2)
})
request(function () {
    console.log(3)
})

接下来看结果:

JavaScript异步函数同步方法

可以看到,这里执行了3次,但是每次输出的顺序都不一致,按照我们想要的结果,是每次都是百分之百顺序执行1 2 3,如果要达到这种效果的话,就得这么写:

/*
*   模拟网络请求的函数
*/
function request(param, callback) {
    let time = Math.random() * 1000                 //为了模拟每次请求的时长不同,这里每次等待随机时间01s
    setTimeout(() => callback(param, time), time)              //请求结束之后,调用回调函数
}

request(1, function (p1, t1) {
    console.log(p1, t1)
    request(2, function (p2, t2) {
        console.log(p2, t2)
        request(3, function (p3, t3) {
            console.log(p3, t3)
        })
    })
})

可以看到,代码一下子变得难维护了很多,现在这里还没有什么逻辑,等每一个request里面加上几十行的逻辑代码,缺点一下子就显现出来了,但是结果却是是没错的:

JavaScript异步函数同步方法

这里先来对比一下两种做法:

  1. 第一种,是针对异步方法来说的,也就是说,三个请求之间没有什么关系,可以同时发起,最终等待时间取决于请求时间最长的请求;
  2. 第二种,是针对同步方法来说的,也就是说,每个请求之间是有依赖关系的,这里的话,request3得等request2请求完毕才能执行,request2要等request1请求完毕才能执行,请求时长为三个请求的总时长;

第一种做法,实际上使用场景非常少,如果不是特殊的业务逻辑,这种使用方式是不被允许的,因为三个请求并没有关系,所以这三个请求,在后台是可以合并为一个请求的,也就是说一次请求就可以完成任务,硬分三次请求的话会留下一些隐患。比如以后要将该功能暴露给外部系统调用,总不能让人家发送三次请求吧,日积月累,也会浪费服务器带宽流量。

第二种实际上是经常要用到的,根据第一个请求的请求结果,处理业务逻辑,然后判断是否需要进行下一步请求。

好在es7给出了一个新的语法,让异步方法同步起来没有那么困难了,看代码(我这里跳过了Promise调用链的说明):

/*
*   模拟网络请求的函数
*/
function request(param) {
    return new Promise((resolve) => {
        let time = Math.random() * 1000                 //为了模拟每次请求的时长不同,这里每次等待随机时间01s
        setTimeout(() => resolve({param, time}), time)              //请求结束之后,调用回调函数
    })
}

async function start() {
    let {param: p1, time: t1} = await request(1)
    console.log(p1, t1)
    let {param: p2, time: t2} = await request(2)
    console.log(p2, t2)
    let {param: p3, time: t3} = await request(3)
    console.log(p3, t3)
}

start()


不知道async/await的使用方法的同学,可以百度看看相关资料,网上已经说得很清楚,我这里就不再赘述了。结果:

JavaScript异步函数同步方法

可以看到结果仍然是正确的,但是里面的代码要比原来的要清爽很多,看起来也没有那么刺眼了……
接下来是本文的重点了,看代码:

/*
*   模拟网络请求的函数
*/
function request(param) {
    return new Promise((resolve) => {
        let time = Math.random() * 1000                 //为了模拟每次请求的时长不同,这里每次等待随机时间01s
        setTimeout(() => resolve({param, time}), time)              //请求结束之后,调用回调函数
    })
}

/*
* 合并异步方法
*/
function compose(middlewares) {
    return function (...args) {
        function dispatch(i) {
            let fn = middlewares[i];
            if (!fn) {
                return Promise.resolve();
            }
            else {
                return Promise.resolve(fn(
                    ...args,
                    function next() {
                        return dispatch(i + 1)
                    }
                ))
            }
        }

        return dispatch(0);
    }
}

let funcs = [
    async function (originalParam, next) {
        let {param: p1, time: t1} = await request(1)
        console.log(p1, t1, originalParam)
        next();
    },
    async function (originalParam, next) {
        let {param: p2, time: t2} = await request(2)
        console.log(p2, t2, originalParam)
        next();
    },
    async function (originalParam, next) {
        let {param: p3, time: t3} = await request(3)
        console.log(p3, t3, originalParam)
        next();
    },
]

compose(funcs)('start')


结果:
JavaScript异步函数同步方法

可以看到结果仍然是正确的,而且我们已经把需要同步的方法统一起来,放在了funcs这个数组中,这个数组中所有的异步函数通过compose会生成一个函数,执行这个函数会依次执行funcs数组中的异步函数,每个异步函数中都可以拿到一个next函数参数,执行该函数,就会执行下一个异步函数,如果在某一次异步函数中发现数据不对,就不执行next,中断了异步函数的同步执行,这样管理起来,所有的异步函数操作起来就方便多了。

这里还实现了koa2中间件的功能,比如看下面代码(其他没变,只变了funcs数组):

let funcs = [
    async function (originalParam, next) {
        console.log('1 start')
        next();
        console.log('1 end')
    },
    async function (originalParam, next) {
        console.log('2 start')
        next();
        console.log('2 end')
    },
    async function (originalParam, next) {
        console.log('3 start')
        next();
        console.log('3 end')
    },
]

结果
JavaScript异步函数同步方法

通过这样的拦截操作,我们可以实现对任意对象任意函数进行拦截功能,详情说明,请见我的博客:基于koa2中间件原理实现拦截任意对象方法的调用