JavaScript的异步编程

JavaScript有几种异步编程的解决方案。

一、回调函数

被传递给其他函数的函数叫作回调函数。回调函数把任务的第二段单独写在一个函数中,待重新执行这个任务时直接调用这个回调函数。

Node中文件操作经常有这样的应用。

使用回调函数时,如果只有一个回调,回调中不会包含其余的回调函数也还好,但是如果回调中包含回调,就会造成所谓的回调地狱,十分不利于代码的review和debug

二、事件监听

事件监听把事件的发生源和事件的发生后的操作进行了分离。

比如ajax中对于load事件和error事件的监听,就可以使用事件监听。

三、发布与订阅

发布与订阅是对事件监听的补充,事件监听只能控制事件的操作,不能控制事件的产生。

而发布与订阅模式可以控制两方面。

在Node中的异步编程使用发布与订阅是常见的。

三、Promise对象

Promise处理少量的异步操作是没有问题的,它和回调函数的模式一样,在处理多个异步操作时,为了让代码看起来像是同步的,把所有的回调都使用了then方法进行封装,

这样使得操作本身的逻辑变的不明显,而且,Promise是不可以取消的,一旦指定了then方法的回调,再发生事件的时候就必须去执行。

之前介绍过Promise(https://www.cnblogs.com/wangtingnoblog/p/js_Promise.html),

四、Generator函数

ES6提供的异步编程的新的解决方案。

Generator函数在之前的文章简单的介绍过(https://www.cnblogs.com/wangtingnoblog/p/js_Generator.html),实际应用中Generator是实现状态机的最佳的数据结构,在异步编程中用到的比较少,

之前介绍过使用Generator函数进行异步编程(https://www.cnblogs.com/wangtingnoblog/p/js_Generator_async.html),

五、async函数

ES7提供的异步编程的终级方案,在Angular中(TypeScript)可以直接使用,其他环境下需要依靠编译器。

5.1 async的原理

  1. async函数可以理解为Generator的语法糖,这时async关键字相当于*,await相当于yield
  2. 也可以把async函数理解为Promise对象的语法糖,因为async函数返回Promise。可以把async函数看做由多个异步操作封装成的一个Promise,await命令就是内部then命令的语法糖。

我们把async理解成Generator的语法糖,来看一下它的实现: 将Generator函数和自动执行器封装在一个函数中。

async function fn(args) {// ....}相当于

function fn(args) {return spawn(function *() {// ...}) }

来看一下spawn的实现原理

 1 function spawn(genF) {
 2   return new Promise(
 3     (resolve, reject) => {
 4       // 执行生成器生成迭代器
 5       const gen = genF();
 6
 7       // 这里把step的参数设置成函数是为了处理异常
 8       function step(nextF) {
 9         let next;
10         try {
11           // 迭代器进入下一个迭代
12           next = nextF();
13         } catch(e) {
14           // 发生异常时返回rejected的Promise
15           return reject(e);
16         }
17         // 判断是否已经迭代结束
18         if (next.done) {
19           // 把async的return的值发送出去,如果async函数没有return,则为undefined
20           resolve(next.value);
21         }
22         // 这里在外层包含Promise.resolve是为了处理await后面跟的不是Promise的情况,这也是async函数的特殊之处
23         Promise.resolve(next.value)
24         .then(
25           // 把迭代器的next方法封装成函数
26           (v) => step(function() { return gen.next(v); })
27         )
28         .catch(
29           // 发生异常时,把迭代器的throw方法封装成函数,这样在调用nextF方法时就能捕获到异常,进入reject发送出去
30           (e) => step(function() { return gen.throw(e); })
31         );
32       }
33       // 启动迭代器
34       step(function() {return gen.next(undefined);})
35     }
36   );
37 }

上面的实现和之前介绍Generator的流程管理几乎一样(spawn添加了异常处理),

async相比于Generator函数的改进体现在如下几点:

  1. 内置执行器: 把Generator的流程管理封装起来,不要开发者进行管理。
  2. 更好的语义: async表示函数中有异步操作,await表示需要等待。
  3. 更广的适用性: Generator的自动流程管理需要yield后面的表达式是函数(参数是回调函数)或者是Promise,而async的await后面则不需要,看代码的第23行。
  4. 返回值是Promise: Generator的返回值是迭代器,而async的返回值是Promise,更易使用。

TypeScript中支持async函数,我们来看一下TypeScript关于async的码源是如何写的:

 1 var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
 2     // 返回一个Promise
 3     return new (P || (P = Promise))(
 4         function (resolve, reject) {
 5             // 异步操作成功之后进行下一次迭代
 6             function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
 7             // 异步操作异常之后抛出错误,在step函数中捕获,再发送出去
 8             function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
 9             // 迭代函数
10             function step(result) {
11                 // 判断迭代器是否已经到了最后
12                 result.done ?
13                     // 把return的值发送出去
14                     resolve(result.value) :
15                     // 这里相对于P.resolve(result.value)),用Peomise封装,保证await后面不是Promise也能正常执行
16                     new P(
17                         function (resolve) { resolve(result.value); }
18                     // 成功和异常处理
19                     ).then(fulfilled, rejected); }
20             step(
21                 // 启动迭代器
22                 (generator = generator.apply(thisArg, _arguments || []))
23                 .next()
24             );
25     });
26 };

发现和上面写的原理差不多,不过对异常做了更加细致的处理。

5.2 async的使用

async函数返回一个Promise对象,可以使用then方法添加回调函数。

当async函数执行时,一旦遇到await就会先返回,等到触发的异步操作完成之后,再执行函数体内后面的语句。

ajax是一个返回Promise的函数

 1   ajax(url) {
 2     return new Promise(
 3       function (resolve, reject) {
 4         const xhr = new XMLHttpRequest();
 5         xhr.open(‘get‘, url, true);
 6         xhr.responseType = ‘json‘;
 7         xhr.setRequestHeader(‘Accept‘, ‘application/json‘);
 8
 9         xhr.addEventListener(‘load‘, function(ev: ProgressEvent) {
10           resolve(xhr.response);
11         });
12         xhr.addEventListener(‘error‘, function(ev: ProgressEvent) {
13           reject(‘发生了错误‘);
14         });
15         xhr.send(null);
16       }
17     );
18   }

 1 async function getFamilies(): Promise<void> {
 2     console.log(‘async start‘);
 3     const nameResponse = await this.ajax(‘http://localhost:3002/users?id=1‘);
 4     console.log(‘第一个await结束‘);
 5     const familiesResponse = await this.ajax(‘http://localhost:3002/family?name=‘ + nameResponse[0].families);
 6     console.log(‘第二个await结束‘);
 7     console.log(‘async end‘);
 8   }
 9 getFamilies();
10 console.log(‘这是在async函数之后的操作‘);

运行结果:

  • async start
  • 这是在async函数之后的操作
  • 第一个await结束
  • 第二个await结束
  • async end

可以看到使用async十分方便而且语义明确。

需要注意的是await后面的异步操作可能是rejected,所以最好把await语句放在try catch语句中。

 1   async getFamilies(): Promise<void> {
 2     console.log(‘async start‘);
 3     let nameResponse;
 4     try {
 5       nameResponse = await this.ajax(‘http://localhost:3001‘);
 6     } catch (e) {
 7       console.log(e);
 8     }
 9
10     console.log(‘第一个await结束‘);
11     const familiesResponse =
12       await this.ajax(‘http://localhost:3003‘).catch(console.log);
13     console.log(‘第二个await结束‘);
14     console.log(‘async end‘);
15   }

有2种方式捕获异常

  1. try catch
  2. Promise 的catch

如果想要并发,可以在await后面使用Promise.all

5.3 async的说明

async函数执行到await时,执行权就会移交给调用async函数的代码,然后去执行下面的代码。直到同步的代码执行完成(栈被清空)。

在执行同步代码的过程中,可能await的异步操作已经完成,但是系统不会立即移交执行权给async函数,它也等同步的代码执行完成之后才会移交。

这是Event Loop决定的。

简单来说,开始时,栈中的函数一个一个执行,当遇到await时,就将await的后面的操作挂起,这时执行栈中的下一个函数,等到await的异步操作完成后,会把挂起的操作放在任务队列中(注意不是立即执行),

栈被清空后,系统从任务队列中提取async其余的操作继续执行。

总结:

  关于js的异步编程主要介绍了async函数的实现方式,其他方式在其他文章中也说明过,这里就不再重复,实际应用不多,有不到位的地方欢迎各位老铁指正。

作成: 2019-02-10

修改:

  1. 2019-02-10 22:58:24

参考:《ES6标准入门》《Learning TypeScript》《深入理解ES6》

原文地址:https://www.cnblogs.com/wangtingnoblog/p/js_async.html

时间: 2024-08-02 14:56:17

JavaScript的异步编程的相关文章

JavaScript中异步编程

一 关于事件的异步 事件是JavaScript中最重要的一个特征,nodejs就是利用js这一异步而设计出来的.所以这里讲一下事件机制. 在一个js文件中,如果要运行某一个函数,有2中手段,一个就是直接调用,比如foo(),第二就是利用事件来触发,这中函数也叫回调函数,比如传递给setTimeout函数和onready属性. 1.setTimeout函数中的事件异步 setTimeout本质上也是一种异步事件,当延迟时间到的时候触发该事件,但是有的有的时候(其实也是大部分时候)都不会按照给定的延

javascript的异步编程方法

一,callback 回调函数 即函数f1和函数f2的关系是f1(f2()); f2作为f1()的回调函数,在f1执行过程中就开始执行f2,先执行线程的主要逻辑,将比较耗时的任务放在后面执行. 回调函数的优点是简单.容易理解和部署 缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数. 二 ,事件监听 事件监听则是和代码的顺序完全无关,只取决于事件是否发生,如f1.bind(“click”,f2)即使当f1的被click时f2执行

javascript的异步编程解决方案收集

缘起 没理解js异步的同学看下面的例子: for (var i = 0; i < 5; i++) { //模拟一个异步操作 setTimeout(() => { console.log(i); }, 1000); } 我们想要的结果是:0,1,2,3,4 结果却出乎意料:5,5,5,5,5 分析 js的特点就是单线程异步非堵塞.需要好好理解这句话:js对于异步操作,不会停下来等待上一个异步操作完成,才进行下一个异步操作. 如果要达到顺序执行,只能用回调:也就是上一个异步操作完成时,再调用下一个

JavaScript异步编程(2)- 先驱者:jsDeferred

原文出处: linkFly   欢迎分享原创到伯乐头条 JavaScript当前有众多实现异步编程的方式,最为耀眼的就是ECMAScript 6规范中的Promise对象,它来自于CommonJS小组的努力:Promise/A+规范. 研究javascript的异步编程,jsDeferred也是有必要探索的:因为Promise/A+规范的制定基本上是奠定在jsDeferred上,它是javascript异步编程中里程碑式的作品.jsDeferred自身的实现也是非常有意思的. 本文将探讨项目js

深入解析Javascript异步编程

这里深入探讨下Javascript的异步编程技术.(P.S. 本文较长,请准备好瓜子可乐 :D) 一. Javascript异步编程简介 至少在语言级别上,Javascript是单线程的,因此异步编程对其尤为重要. 拿nodejs来说,外壳是一层js语言,这是用户操作的层面,在这个层次上它是单线程运行的,也就是说我们不能像Java.Python这类语言在语言级别使用多线程能力.取而代之的是,nodejs编程中大量使用了异步编程技术,这是为了高效使用硬件,同时也可以不造成同步阻塞.不过nodejs

5分种让你了解javascript异步编程的前世今生,从onclick到await/async

javascript与异步编程 为了避免资源管理等复杂性的问题,javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是为浏览器设计的GUI编程语言,GUI编程的特性之一是保证UI线程一定不能阻塞,否则体验不佳,甚至界面卡死. 一般安卓开发,会有一个界面线程,一个后台线程,保证界面的流畅.由于javascript是单线程,所以采用异步非阻塞的编程模式,javascript的绝大多数api都是异步api. 本文是本人的一个

探索Javascript异步编程

异步编程带来的问题在客户端Javascript中并不明显,但随着服务器端Javascript越来越广的被使用,大量的异步IO操作使得该问题变得明显.许多不同的方法都可以解决这个问题,本文讨论了一些方法,但并不深入.大家需要根据自己的情况选择一个适于自己的方法. 笔者在之前的一片博客中简单的讨论了Python和Javascript的异同,其实作为一种编程语言Javascript的异步编程是一个非常值得讨论的有趣话题. JavaScript 异步编程简介 回调函数和异步执行 所谓的异步指的是函数的调

前端排序算法总结;前端面试题2.0;JavaScript异步编程

1.前端 排序算法总结 排序算法可能是你学编程第一个学习的算法,还记得冒泡吗? 当然,排序和查找两类算法是面试的热门选项.如果你是一个会写快排的程序猿,面试官在比较你和一个连快排都不会写的人的时候,会优先选择你的.那么,前端需要会排序吗?答案是毋庸置疑的,必须会.现在的前端对计算机基础要求越来越高了,如果连排序这些算法都不会,那么发展前景就有限了.本篇将会总结一下,在前端的一些排序算法. https://segmentfault.com/a/11... 2.前端面试题 V2.0 详见: 这是一份

究竟什么是异步编程?

在我们的工作和学习当中,到处充满了异步的身影,到底什么是异步,什么是异步编程,为什么要用异步编程,以及经典的异步编程有哪些,在工作中的场景又有什么,我们一点点深入的去学习. 什么是异步编程? 有必要了解一下,什么是异步编程,为什么要异步编程. 先说一个概念异步与同步.介绍异步之前,回顾一下,所谓同步编程,就是计算机一行一行按顺序依次执行代码,当前代码任务耗时执行会阻塞后续代码的执行. 同步编程,即是一种典型的请求-响应模型,当请求调用一个函数或方法后,需等待其响应返回,然后执行后续代码. 一般情