JavaScript异步编程技术比较:Async、Generator、Promise、CPS

ES7之后JavaScript提供了多种异步编程的实现方式,接下来通过例子比较一下各种方式在使用上的不同。

我们将通过异步的方式调用下面Sleep函数

// sleep: number -> Promise<number>
function sleep(ms){
  return new Promise(resolve =>
    setTimeout((()=>resolve(ms)), ms));
}

1. async/await

async function main(){
  let a = await sleep(1000);
  alert(`${a}ms passed`); // 1000ms passed
  let b = await sleep(2000);
  alert(`${b}ms passed`); // 2000ms passed
  let [c, d] = await Promise.all([
     sleep(3000),
     sleep(4000)
  ]);
  alert(`${Math.max(c, d)}ms passed`); // 4000ms passed
  alert('done');
}

main(); // return Promise

async是实现异步调用最简洁的方式,async内部可以通过await使一个异步调用转为同步。想要同步多个异步结果时,可以使用Promise.all配合await实现。

2. generator/yield

// main: void -> Promise<void>
let main = async(function* _main(){
  let a = yield sleep(1000);
  alert(`${a}ms passed`);
  let b = yield sleep(2000);
  alert(`${b}ms passed`);
  let [c, d] = yield Promise.all([
     sleep(3000),
     sleep(4000)
  ]);
  alert(`${Math.max(c, d)}ms passed`);
  alert('done');
});

// async: (void -> Generator) -> (void -> Promise)
function async(generatorFunc) {
  let generator = generatorFunc();
  let onResolved = arg =>{
    let result = generator.next(arg);
    if (result.done) {
      return result.value;
    } else {
      return result.value.then(onResolved);
    }
  }
  return onResolved;
}

main(); // return Promise<void>

async函数接受一个generator函数返回Promise。generator函数_main内部调用的Promise成功后会调用next,进行后需处理,相当于用yield模仿了await的效果。整体上用generator/yield模拟了async/await的效果。

3. Promise/then

// main: void -> Promise<void>
function main(){
  return sleep(1000).then((a)=>{
    alert(`${a}ms passed`);
    return sleep(2000).then((b)=>{
      alert(`${b}ms passed`);
      return Promise.all([sleep(3000), sleep(4000)]).then(([c, d])=>{
        alert(`${Math.max(c, d)}ms passed`);
        alert('done');
        return;
      });
    });
  });
});

main(); // return Promise<void>

使用Promise/then的问题是无法避免回调地狱,所以ES7出现async/await之后,以下这样的代码

function main(){
  return getA().then((a)=>
    getB().then((b)=>
      a + b));
}

变更多的用下面这样的代码代替

async function main(){
  const a = await getA();
  const b = await getB();
  return a + b;
}

4. CPS

首先实现CPS版本的sleep如下:

// sleep: number -> (number -> void) -> void
function sleep(ms, cb){
  setTimeout((()=> cb(ms)), ms);
  return;
}

然后实现异步调用如下: 

// main: void -> Promise<void>
function main(){
  sleep(1000, (a)=>{
    alert(`${a}ms passed`);
    sleep(2000, (b)=>{
      alert(`${b}ms passed`);
      let waitAll = genWaitAll(next);
      sleep(3000, waitAll());
      sleep(4000, waitAll());
      function next([c, d]){
        alert(`${Math.max(c, d)}ms passed`);
        alert('done');
        return;
      }
    });
  });
};

// genWaitAll: ([a] -> void) -> (void -> (a -> void))
function genWaitAll(next){
  let results = [];
  let counter = 0
  return ()=>{
    let i = counter;
    counter++;
    return (ms)=>{
      results[i] = ms;
      counter--;
      if (counter === 0){
        next(results);
      }
    };
  }
};

main(); // return void;

CPS本质就是利用回调实现异步编程,所以会出现回调地狱的问题,同时为了实现Promise.all的效果需要手动实现genWaitAll()

5. 总结

我们通过例子对比了JS各种异步编程的实现方案,需要说明的是,这些方案诞生的时间与我们的介绍是相反的,一句话总结他们之间的关系:CPS通过回调实现了最朴素的异步编程方案;ES6提出Promise,在CPS的基础上实现了then、Promise.all、Promise.race等功能、使用起来更加方便;generator可以帮助Promise中实现异步编程中的同步;ES7中出现的async/await相当于generator/yield的语法糖,可以更简单地实现异步编程中的同步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fundroid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值