串行异步请求

1,440 阅读5分钟

前言

        我们都知道js是单线程的,其使用了异步模式去解决‘堵塞’行为。在实际开发中,经常会碰到一个异步操作必须要在另一个异步操作之后执行,比如:图片上传oss需要先获取签名再上传,或者微信小程序获取授权需要先获取code这些操作。接下来会介绍几种实现 串行异步 操作的方式。

串行异步请求

callback

        假设存在 callback1 和 callback2 两个异步函数(此处使用延时器模拟异步操作) callback2 依赖于 callback1 的返回值。

        回调函数的方式即在 callback1 函数内部执行完成后调用 callback2,将callback所需要的参数直接传入。

let callback1 = () => {
    setTimeout(() => {
        console.log("callback1")
        callback2("callback1")
    }, 1000);
}
let callback2 = (data) => {
    setTimeout(() => {
        console.log("callback2","传入的数据"+data)
    }, 500);
}
callback1()

        这种方式实现起来简单,容易理解,缺点是不利于代码的阅读和维护,当依赖的层级变多时会陷入回调地狱。

promise

        同上,假如 promise2 依赖 promise1 ,可以对两个异步函数进行改造,使每一个异步操作都返回一个promise对象,并且在resolve返回信息,那么就可以在promise1的then()回调之后调用promise2。

        Promise对象共有3种状态:pending 初始状态;fulfilled: 意味着操作成功完成;rejected: 意味着操作失败;只有异步操作的结果可以改变这个状态且状态一旦发生改变就不会再发生变化,任何时候都可以得到这个结果。 resolve()时传入的参数,可以在promise.then()中获取到。

let promise1 = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("promise1")
        resolve("promise1")
    }, 2000);
})
let promise2 = (data) => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("promise2", "传入的数据" + data)
        resolve()
    }, 1500);
})
promise1()
    .then((res) => {
        return promise2(res)
    })
    .then(() => {
    })

        Promise的调用方式解决了回调地狱和异步回调不能有效的使用throw的问题,采用了链式调用的方式,可以清晰的看出异步操作之间的依赖关系。

Generator

        Generator是ES6新增的一个生成器,是一种特殊的函数。与普通函数不同的是,定义时需要在函数名前添加‘*’。并且在调用Generator函数后,该函数并不执行,而是会生成一个遍历器对象,返回的也不是函数的运行结果,而是一个指向内部状态的指针对象,当调用指针对象的next()方法时,指针指向下一个状态。Generator函数可以通过yield关键字将函数挂起。generator是分段进行的,yield语句是暂停执行的标记,而next()方法可以恢复执行。         next()方法可以带参,generator1函数将返回值通过next()传入,该参数就会被当作上一个yield表达式的返回值,然后被generator2接收。

let generator1 = function () {
    setTimeout(() => {
        console.log("generator1")
        it.next("generator1")
    }, 2000);
}
let generator2 = function (data) {
    setTimeout(() => {
        console.log("generator2", "传入的数据" + data)
        it.next()
    }, 1500);
}
let generator = function* () {
    let result = yield generator1()
    yield generator2(result)
}
let it = generator()
it.next()

generator+co

        co函数是TJ大佬发布的一个自动执行generator函数的工具,省去了书写执行器的过程,其返回一个Promise对象。

let co = require('co')
let generator3 = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("generator3")
        resolve("generator3")
    }, 2000);
})
let generator4 = (data) => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("generator4", "传入的数据" + data)
        resolve("generator4")
    }, 2000);
})
let generatorByCo = function* () {
    let result = yield generator3()
    let result2 = yield generator4(result)
    return result2
}
co(generatorByCo).then(res => {
    console.log(res)
})
// 等同于
// let itByCo = generatorByCo()
// itByCo.next().value.then(res=>{
//     itByCo.next(res).value.then(res=>{
//        let data =  itByCo.next(res).value
//     })
// })

async/await

        async/await是es7发布的异步终极解决方案,属于generator函数和Promise的语法糖,将generator函数和执行器包装到一个函数内。async/await的出现使得异步流程如同同步代码一样流程清晰。async表示函数里有异步操作,await表示后面的表达式需要等待结果。async函数返回的Promise对象必须等待内部所有await后面的Promise表达式执行完,才会发生改变,除非碰到return或者抛出异常。

        asyncAwait1被await关键字修饰,则程序会一直等待其返回结果,拿到结果后将参数传入asyncAwait2。代码变成了同步的书写形式。

let asyncAwait1 = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("asyncAwait1")
        resolve("asyncAwait1")
    }, 1000);
})
let asyncAwait2 = (data) => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("asyncAwait2", "传入的数据" + data)
        resolve()
    }, 1000);
})
async function async() {
    let result = await asyncAwait1()
    await asyncAwait2(result)
}
async()

事件监听

采用事件驱动模型实现,任务执行顺序不取决于代码的执行顺序,而取决于某个事件是否发生。(events是node的内置模块,所以采用node环境下的写法)。 event1执行结束后触发end事件,这时捕获到end事件会立即执行event2。,并将参数传递进去。

let events = require("events")
let emitter = new events.EventEmitter()
let event1 = () => {
    setTimeout(() => {
        console.log("event1")
        emitter.emit("end","event1")
    }, 1000);
}
let event2 = (data) => {
    setTimeout(() => {
        console.log("event2", "传入的数据" + data)
    }, 1000);
}
emitter.on("end",event2)
event1()

结尾

  1. 从 callback 到 promise 再到 generator 最后到 async/await 这也是js异步编程的发展历程。每一个新的技术迭代出现都是对之前的优化,callback解决了同步代码阻塞的问题;promise避免了callback回调地狱的缺点;generotor则交出了函数的执行权,可以在需要暂停的地方暂停,能够控制函数的执行。generotor函数异步编程的书写方式已经类似于同步代码书写方式了;async/await则内置了执行器解决generator需要next一步步执行的问题,使得调用方式和普通函数一样,代码更为清晰;
  2. 异步编程需要注意异常的处理,promise、generator、async/await都提供了捕获异常的能力;

示例代码 github