全面掌握异步解决方案

前言

在我们日常项目开发中,我们在做业务开发的时候会涉及到复选框组的功能,所以封装了这个复选框组的组件。

Promise

长久以来,我们一直期望着一种既能实现异步、又可以确保我们的代码好写又好看的解决方案出现。带着这样的目标,经过反复的探索,我们终于迎来了 Promise。 用 Promise 实现异步,我们这样做


const https = require('https'); 
function httpPromise(url){ 
    return new Promise(function(resolve,reject){ 
        https.get(url, (res) => { 
            resolve(data); 
        }).on("error", (err) => { 
            reject(error); 
        }); 
    }) 
} 
httpPromise().then(function(data){ 
    }).catch(function(error){
    })

可以看出,Promise 会接收一个执行器,在这个执行器里,我们需要把目标的异步任务给”填进去“。 在 Promise 实例创建后,执行器里的逻辑会立刻执行,在执行的过程中,根据异步返回的结果,决定如何使用 resolve 或 reject 来改变 Promise实例的状态。 Promise 实例有三种状态:

• pending 状态,表示进行中。这是 Promise 实例创建后的一个初始态; • fulfilled 状态,表示成功完成。这是我们在执行器中调用 resolve 后,达成的状态; • rejected 状态,表示操作失败、被拒绝。这是我们在执行器中调用 reject后,达成的状态。

在上面这个例子里,当我们用 resolve 切换到了成功态后,Promise 的逻辑就会走到 then 中的传入的方法里去;用 reject 切换到失败态后,Promise 的逻辑就会走到 catch 传入的方法中去。

这样的逻辑,本质上与回调函数中的成功回调和失败回调无异。但这种写法毫无疑问大大地提高了代码的质量。最直接的例子就是当我们进行大量的异步链式调用时,回调地狱不复存在了。取而代之的,是层级简单、赏心悦目的 Promise 调用链:


httpPromise(url1) 
    .then(res => { 
        console.log(res); 
        return httpPromise(url2); 
    }) 
    .then(res => { 
        console.log(res); 
        return httpPromise(url3); 
    }) 
    .then(res => { 
        console.log(res); 
        return httpPromise(url4); 
    }) 
    .then(res => console.log(res));

Generator

除了 Promise, ES2015 还为我们提供了 Generator 这个好帮手~ 

Generator 一个有利于异步的特性是,它可以在执行中被中断、然后等待一段时间再被我们唤醒。通过这个“中断后唤醒”的机制,我们可以把 Generator看作是异步任务的容器,利用 yield 关键字,实现对异步任务的等待。

上面的例子完全可以写成

function* httpGenerator() { 
    let res1 = yield httpPromise(url1) 
    console.log(res); 
    let res2 = yield httpPromise(url2) 
    console.log(res); 
    let res3 = yield httpPromise(url3) 
    console.log(res); 
    let res4 = yield httpPromise(url4) 
    console.log(res); 
}

当然啦,单纯这么改还不够,我们还需要在调用层面再完善一下才能让这个生成器如期运行起来。
但在完善之前,咱们就单纯看这种写法,是不是比 Promise 链式调用更好看、更清晰了?这时候你一眼看过去就知道这段逻辑在干嘛,而不必再对所谓的“链”作分析

function runGenerator(gen) { 
    var it = gen(), ret; // 创造一个立即执行的递归函数 
    (function iterate(val){ 
        ret = it.next(val); 
        if (!ret.done) { 
            // 如果能拿到一个 promise 实例 
            if ("then" in ret.value) { 
                // 就在它的 then 方法里递归调用 
                iterate ret.value.then( iterate ); 
            } 
        } 
    })(); 
} 
runGenerator(httpGenerator)

大家一起来看下 runGenerator 这个方法,当我们把 httpGenerator 传进去后,会发生如下过程:

  1. 为传入的 Generator 创建它对应的迭代器 it。然后,我们第一次调用 iterate 函数,入参为空。
  2. iterate 函数内部,调用 it 的 next 方法,生成器函数开始执行,执行到第一个 yield 关键字处的逻辑执行完后暂停。它会返回一个包含了 httpPromise(url1) 这个调用返回的 promise对象(我们下文称 promise1)、以及一个 done: false 的标识,用来表示当前生成器函数内部的逻辑还没执行完(大致如下):
{ 
  value:
   Promise {
     <pending>,
     ...// 省略一系列 promise 对象关联信息
   },
  done: false
}
  1. 因为 done 为 false,所以我们会进一步判断当前拿到的是否是一个 promise 对象(根据它有没有 then 属性)。判断为真后,我们在 promise1 的 then 方法里传入 iterate 函数本身。

  2. promise1 的 then 方法里的 iterate 函数调用,拿到了 promise1 的返回结果(即针对 url1 的请求结果)作为入参。it.next 被第二次调用,生成器函数被“唤醒”了。注意,被“唤醒”后的生成器函数,按照流程走,它执行的第一个语句就是:

    let res1 = yield httpPromise(url1)

    这一步会把 next(val) 中的 val 传给 res1,而 val,恰恰就是 promise1 的返回结果。一切正如我们所预期~~

    而后,生成器函数会继续执行到第二个 yield 关键字处,执行完后暂停。

    此时 next 方法返回一个包含了 httpPromise(url2) 这个调用返回的 promise 对象(我们下文称 promise2)、以及一个 done: false 的标识(用来表示当前生成器函数内部的逻辑还没执行完)。因为 done 为 false,所以我们会进一步判断当前拿到的是否是一个 promise 对象(根据它有没有 then 属 性)。判断为真后,我们在 promise2 的 then 方法里传入 iterate 函数本身。

  3. 循环上述过程过程,直到生成器内部逻辑执行完为止。
    通过“自动执行”生成器函数对应迭代器的 next 方法,我们把异步的写法进一步优化了。它不再需要地狱般的回调,甚至不再需要 Promise 长长的链式调用,而是可以像写同步代码一样简单、清晰地实现异步特性!

    不过仔细想想,咱们这个 runGenerator 其实非常简陋,它虽然体现了自动执行的思想,却不具备通用性,无法兼容更多场景——确实,要写出一个完整周到的 runGenerator 函数,不是一件轻松的事情。但是有一个好用的 runGenerator,又确实是广大开发者的强诉求。于是我们有了一个叫 co 的库,专门来封装自执行这一层的逻辑:

    const co = require('co');
    co(httpGenerator());

    这里的 co,大家就可以把它看作是一个加强版的 runGenerator。我们只需要在代码里引入 co 库,然后把写好的 generator 传进去,就可以轻松地实现 generator 异步了。

    Async/Await

就当大家正在纷纷感慨 co 真好使,generator + promise + co 的异步方案真优雅时,更强的家伙出现了。这玩意儿甚至甩开了 co、甩开了 generator,有了它,你什么都不用操心,只需要写几个关键字,就能把异步代码处理得像同步代码一样优雅!这玩意儿就是 async/await。

它的用法非常简单。首先,我们用 async 关键字声明一个函数为“异步函数”:

async function httpRequest() {

}

然后,我们就可以在这个函数内部使用 await 关键字了:

这个 await 关键字很绝,它的意思就是“我要异步了,可能会花点时间,后面的语句都给我等着”。当我们给 httpPromise(url1) 这个异步任务应用了 await 关键字后,整个函数会像被“yield”了一样,暂停下来,直到异步任务的结果返回后,它才会被“唤醒”,继续执行后面的语句。

是不是觉得这个“暂停”、”唤醒“的操作,和 generator 异步非常相似?事实上,async/await 本身就是 generator 异步方案的语法糖。它的诞生主要就是为了这个单纯而美好的目的——让你写得更爽,让你写出来的代码更美。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值