# 现代方案

广大开发者苦回调地狱已久,早就想翻身了。这节一口气说完三个现代异步方案:

Promise —> Generator —> async/await。

# 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){
  // 处理成功后返回的 data
}).catch(function(error){
  // 处理失败后返回的 error
})

Promise的方案是:接收一个匿名函数,返回一个Promise函数实例。在函数体中写异步任务,这个任务会立即执行。

执行过程中会有3种状态:

  • pending 状态,表示进行中
  • fulfilled 状态,表示成功完成
  • rejected 状态,表示操作失败、被拒绝

匿名函数有两个参数:resolve, reject

函数体内执行 resolve(),会将状态设置为fulfilled,逻辑会走到then中去;执行 rejected(),状态设置为rejected,逻辑则走到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));

清新脱俗有木有!

明白了原理,试试手动实现一个Promise:模拟Promise

# Generator

Generator 函数不同于普通函数,它会返回一个遍历器对象,循环遍历函数体内的状态

那么怎样和普通函数区别开来?怎样定义状态?这里要介绍Generator的两个特征:

  • function关键字与函数名之间有一个星号
  • 函数体内使用yield关键字定义状态
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

例子中,内部有两个状态(hello和world),以及return的状态(ending),共3个状态

函数调用,返回hw。这个hw并不是函数执行结果,事实上函数并没有执行,返回的是一个遍历器对象,用于遍历内部状态的指针

所有上面代码可以这么写:

function* httpGenerator() {
  let res1 = yield httpPromise(url1)
  console.log(res);
  let res2 = yield httpPromise(url2)
  console.log(res);
}
var it = httpGenerator();

由于只返回了迭代器,函数体的代码并未执行,所以我们执行的方式只有一个,调用 it 的 next方法

但是也不能一直手动调 next 方法,我们需要写一个函数让它自动调用完所有的 next 返回结果:

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 只是一个雏形,体现的就是自动执行的思想。而这个雏形最典型的完整版,就是大名鼎鼎的 co模块,你一定用过吧~

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

# Async/Await

终极异步处理方案,两个关键字,就可以实现同步方式写异步代码

它的用法非常简单,写一个请求案例:

async function httpRequest() {
  let res1 = await httpPromise(url1)
  console.log(res1)
}  

其实,async/await 本身是 generator 异步方案的语法糖,但是语法上更直观

注:Promise 的错误需要通过回调函数捕获,try catch 是行不通的。而 async/await 和 generator 允许 try/catch。

更新时间:: 10/27/2021, 9:34:24 AM