JavaScript异步函数同步方法

首先这里非常感谢慕课网蜗牛老湿,本文内容是基于老师讲解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异步函数同步方法_第1张图片

可以看到,这里执行了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异步函数同步方法_第2张图片

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

  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异步函数同步方法_第3张图片

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

/*
*   模拟网络请求的函数
*/
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异步函数同步方法_第4张图片

可以看到结果仍然是正确的,而且我们已经把需要同步的方法统一起来,放在了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异步函数同步方法_第5张图片

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

你可能感兴趣的:(前端)