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

淩亂°似流年 2023-07-05 13:26 77阅读 0赞

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

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

  1. // sleep: number -> Promise<number>
  2. function sleep(ms){
  3. return new Promise(resolve =>
  4. setTimeout((()=>resolve(ms)), ms));
  5. }

1. async/await

  1. async function main(){
  2. let a = await sleep(1000);
  3. alert(`${a}ms passed`); // 1000ms passed
  4. let b = await sleep(2000);
  5. alert(`${b}ms passed`); // 2000ms passed
  6. let [c, d] = await Promise.all([
  7. sleep(3000),
  8. sleep(4000)
  9. ]);
  10. alert(`${Math.max(c, d)}ms passed`); // 4000ms passed
  11. alert('done');
  12. }
  13. main(); // return Promise

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

2. generator/yield

  1. // main: void -> Promise<void>
  2. let main = async(function* _main(){
  3. let a = yield sleep(1000);
  4. alert(`${a}ms passed`);
  5. let b = yield sleep(2000);
  6. alert(`${b}ms passed`);
  7. let [c, d] = yield Promise.all([
  8. sleep(3000),
  9. sleep(4000)
  10. ]);
  11. alert(`${Math.max(c, d)}ms passed`);
  12. alert('done');
  13. });
  14. // async: (void -> Generator) -> (void -> Promise)
  15. function async(generatorFunc) {
  16. let generator = generatorFunc();
  17. let onResolved = arg =>{
  18. let result = generator.next(arg);
  19. if (result.done) {
  20. return result.value;
  21. } else {
  22. return result.value.then(onResolved);
  23. }
  24. }
  25. return onResolved;
  26. }
  27. main(); // return Promise<void>

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

3. Promise/then

  1. // main: void -> Promise<void>
  2. function main(){
  3. return sleep(1000).then((a)=>{
  4. alert(`${a}ms passed`);
  5. return sleep(2000).then((b)=>{
  6. alert(`${b}ms passed`);
  7. return Promise.all([sleep(3000), sleep(4000)]).then(([c, d])=>{
  8. alert(`${Math.max(c, d)}ms passed`);
  9. alert('done');
  10. return;
  11. });
  12. });
  13. });
  14. });
  15. main(); // return Promise<void>

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

  1. function main(){
  2. return getA().then((a)=>
  3. getB().then((b)=>
  4. a + b));
  5. }

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

  1. async function main(){
  2. const a = await getA();
  3. const b = await getB();
  4. return a + b;
  5. }

4. CPS

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

  1. // sleep: number -> (number -> void) -> void
  2. function sleep(ms, cb){
  3. setTimeout((()=> cb(ms)), ms);
  4. return;
  5. }

然后实现异步调用如下:

  1. // main: void -> Promise<void>
  2. function main(){
  3. sleep(1000, (a)=>{
  4. alert(`${a}ms passed`);
  5. sleep(2000, (b)=>{
  6. alert(`${b}ms passed`);
  7. let waitAll = genWaitAll(next);
  8. sleep(3000, waitAll());
  9. sleep(4000, waitAll());
  10. function next([c, d]){
  11. alert(`${Math.max(c, d)}ms passed`);
  12. alert('done');
  13. return;
  14. }
  15. });
  16. });
  17. };
  18. // genWaitAll: ([a] -> void) -> (void -> (a -> void))
  19. function genWaitAll(next){
  20. let results = [];
  21. let counter = 0
  22. return ()=>{
  23. let i = counter;
  24. counter++;
  25. return (ms)=>{
  26. results[i] = ms;
  27. counter--;
  28. if (counter === 0){
  29. next(results);
  30. }
  31. };
  32. }
  33. };
  34. 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的语法糖,可以更简单地实现异步编程中的同步。

发表评论

表情:
评论列表 (有 0 条评论,77人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JavaScript 异步编程

    JS单线程的原因 如果多线程同时操作了dom,浏览器并不知道以谁为准。 优点:安全。 缺点:如果有耗时任务,会出现假死现象。 所以为了解决以上问题,JS有俩种

    相关 JavaScript异步编程

    简介 JavaScript是一种单线程执行的脚本语言,为了不让一段JavaScript代码执行时间过久,阻塞UI的渲染或者是鼠标事件处理,通常会采用一种异步的[编程][Lin