异步操作

关于异步操作的解释,这段话摘自于javascript基础

单线程模型指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。 注意,JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript 引擎有多个线程,单个脚本只能在一个线程上运行(称为主线程),其他线程都是在后台配合。 JavaScript 之所以采用单线程,而不是多线程,跟历史有关系。JavaScript 从诞生起就是单线程,原因是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。如果 JavaScript 同时有两个线程,一个线程在网页 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?是不是还要有锁机制?所以,为了避免复杂性,JavaScript 一开始就是单线程,这已经成了这门语言的核心特征,将来也不会改变。 这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段 JavaScript 代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。JavaScript 语言本身并不慢,慢的是读写外部数据,比如等待 Ajax 请求返回结果。这个时候,如果对方服务器迟迟没有响应,或者网络不通畅,就会导致脚本的长时间停滞。 如果排队是因为计算量大,CPU 忙不过来,倒也算了,但是很多时候 CPU 是闲着的,因为 IO 操作(输入输出)很慢(比如 Ajax 操作从网络读取数据),不得不等着结果出来,再往下执行。JavaScript 语言的设计者意识到,这时 CPU 完全可以不管 IO 操作,挂起处于等待中的任务,先运行排在后面的任务。等到 IO 操作返回了结果,再回过头,把挂起的任务继续执行下去。这种机制就是 JavaScript 内部采用的“事件循环”机制(Event Loop)。 单线程模型虽然对 JavaScript 构成了很大的限制,但也因此使它具备了其他语言不具备的优势。如果用得好,JavaScript 程序是不会出现堵塞的,这就是为什么 Node 可以用很少的资源,应付大流量访问的原因。 为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。 程序里面所有的任务,可以分成两类:同步任务(synchronous)和异步任务(asynchronous)。 同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。 异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。 举例来说,Ajax 操作可以当作同步任务处理,也可以当作异步任务处理,由开发者决定。如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。 JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。) 首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。 异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。 JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。维基百科的定义是:“事件循环是一个程序结构,用于等待和发送消息和事件(a programming construct that waits for and dispatches events or messages in a program)”。 异步操作的流程控制
如果有多个异步操作,就存在一个流程控制的问题:如何确定异步操作执行的顺序,以及如何保证遵守这种顺序。
1 串行执行 流程控制函数,让它来控制异步任务,一个任务完成以后,再执行另一个。这就叫串行执行。
2 并行执行 流程控制函数也可以是并行执行,即所有异步任务同时执行,等到全部完成以后,才执行final函数。
相比而言,上面的写法只要一秒,就能完成整个脚本。这就是说,并行执行的效率较高,比起串行执行一次只能执行一个任务,较为节约时间。但是问题在于如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。因此有了第三种流程控制方式。
3 并行与串行的结合 设置一个门槛,每次最多只能并行执行n个异步任务,这样就避免了过分占用系统资源。 。变量running记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0,就表示所有任务都执行完了,这时就执行final函数。 在ES6中,Promise语法完全解决了串并行麻烦的问题,有Promise.All 跟Promise.ract

以上的说明中就说明javascript异步操作的所有关键点,异步任务,任务队列,事件循环,Promise。还有比较重要的就是Promise新的语法糖,async跟await。

异步任务

简单来说:异步任务就是可以挂起的任务。假设我们现在点了个外卖,外卖要半小时后才能送到,我们并不需要一直等奶茶来才能做其他事情,我们可以在等奶茶的事件内做任何事情,等奶茶到了我们再喝奶茶。这就是异步任务,同步任务是指你煮泡面,必须先烧开水你才能煮泡面,没烧开前无法煮泡面,必须按顺序执行。

javascipt的异步任务实现方式有三种,事件模型,回调任务及Promise。

  1. 事件模型

例如按钮点击事件,只有按钮被按下才会触发事件并将事件加入到任务队列底部等待其他事件完成后执行,虽然有点像同步任务,但是它产生的事件是被放在任务队列底部最后执行,也算异步任务。

  1. let button = document.getElementById("my-btn");
  2. button.onclick = function(event) {
  3. console.log("Clicked");
  4. };

事件可以很好地工作于简单的交互,但将多个分离的异步调用串联在一起却会很麻烦,因为 必须追踪每个事件的事件对象(例如上例中的 button )。此外,你还需确保所有的事件处 理程序都能在事件第一次触发之前被绑定完毕。例如,若 button 在 onclick 被绑定之前就 被点击,那就不会有任何事发生。因此虽然在响应用户交互或类似的低频功能时,事件很有 用,但它在面对更复杂的需求时仍然不够灵活

  1. 回调函数

回调函数模式类似于事件模型,因为异步代码也会在后面的一个时间点才执行。不同之处在于需要调用的函

数(即回调函数)是作为参数传入的,如下所示:

  1. readFile("example.txt", function(err, contents)
  2. {
  3. if (err){
  4. throw err;
  5. }
  6. console.log(contents);
  7. });
  8. console.log("Hi!");

执行readFile函数,当readFile函数有响应后,将其回调函数添加到任务队列底部作为readFile方法执行完成后的响应。回调函数广泛应用于各种异步操作,如ajax,node等都是用回调函数的形式进行异步操作。回调函数也能很方便的进行串联,不像事件模型不好串联。不过回调函数有个缺点就是当异步操作很多的时候,就会陷入回调地狱。

  1. //假设所有的方法都是异步操作
  2. method1(function(result){
  3. method2(function(result){
  4. method3(function(result){
  5. ......
  6. })
  7. })
  8. })

多个异步操作串联就会导致内部层层叠叠的回调函数。当然这种方法并不会报错,只是维护起来会特别麻烦。


任务队列

即上述所说的浏览器异步更新的机制。

JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。)

首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。
1603003177(1).png

浏览器任务还分为宏任务跟微任务

  • 宏任务:script全部代码、setTimeoutsetIntervalsetImmediate(浏览器暂时不支持,只有IE10支持,具体可见[MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setImmediate))、I/OUI Rendering
  • 微任务:Process.nextTick(Node独有)PromiseObject.observe(废弃)MutationObserver

当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。

简单来说 执行完一个宏任务,再执行这个宏任务中产出的所有微任务,当微任务队列为空时,再执行剩余的宏任务,反复循环。宏任务是一个个按顺序执行,微任务是一队队执行

举个栗子:

  1. <scirpt>
  2. console.log('script start');
  3. setTimeout(function() {
  4. console.log('setTimeout');
  5. }, 0);
  6. Promise.resolve().then(function() {
  7. console.log('promise1');
  8. }).then(function() {
  9. console.log('promise2');
  10. });
  11. console.log('script end');
  12. </script>
  13. //'script start' 'script end' 'promise1' 'promise2' 'setTimeout'
  14. // 第一步:执行scipt宏任务 先输出'script start',再输出'script end'(同步执行的) 并将过程中的宏任务微任务划分到任务队列中
  15. // 第二步 在执行scipt完后,微任务队列中存在Promise微任务,所以第二轮输出 'promise1'
  16. // 第三步 promise1执行完毕后又有一个promise2的微任务,微任务队列不为空,执行promise2,输出'promise2'
  17. // 第四步 所有微任务执行完毕,执行下一个宏任务setTimeout,输出'setTimeout'

这里就不细讲了,这边推荐几篇写的很不错的文章 一次弄懂eventloop浏览器事件循环,
这里说下promise.then的异步操作,then

这里送上一个测试题:eventloop面试题 如果能全答对,其实也差不多弄懂这个知识点了。

Promise

以下这段话摘自于深入理解ES6这本书

Promise 是一个构造函数,我们生成Promise的实例对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。 resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。 Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

  1. let promise = new Promise(function(resolve, reject) {
  2. console.log('Promise');
  3. resolve();
  4. });
  5. promise.then(function() {
  6. console.log('resolved.');
  7. },function(err){
  8. console.log(err)
  9. });
  10. console.log('Hi!');
  11. // Promise
  12. // Hi!
  13. // resolved

Promise在进行new的时候,会立即执行传入的函数,为一个同步任务。只有pomise被then方法调用后,代表异步任务的结果出现,将pending状态改变,then方法内的两个参数分别对应resolve跟reject。

Promise.then(resolve,reject) 中reject可以省略不写,promise提供了catch方法来捕获reject状态。

  1. let promise = new Promise(function(resolve, reject) {
  2. reject();
  3. });
  4. //当promise被reject后,catch会捕获到reject
  5. promise.then(function() {
  6. console.log('resolved.');
  7. }).catch(err){
  8. console.log(err)
  9. };

Promise可以被链式调用。执行完一个then或catch方法,返回的是一个新的promise对象,且仅当前一个 Promise被完成或拒绝后,下一个Promise才会被执行。每个promise可以返回结果值传递给下一个promis。catch方法可以捕获到整条链上未被处理的抛出的异常(reject方法只有在生成promise的时候可以使用,你可以用Promise.reject()方法生成reject状态)。

  1. let p1 = new Promise(function(resolve, reject) {
  2. resolve(42);
  3. });
  4. p1.then(function(value) {
  5. console.log(value); //42
  6. return value + 1;
  7. }).then(function(value) {
  8. console.log(value);//43
  9. return value
  10. }).then(function(value){
  11. throw new Error(value)
  12. }).catch(function(error){
  13. console.log(error.message) //43
  14. });

从完成或拒绝处理函数中返回一个基本类型值,能够在 Promise 之间传递数据,但若你返回的试一个promise的对象,则需要等这个对象的状态决定以后,才能进行下一步操作。

  1. let p1 = new Promise(function(resolve, reject) {
  2. resolve(42);
  3. });
  4. let p2 = new Promise(function(resolve, reject) {
  5. resolve(43);
  6. });
  7. //在这里p1返回的是一个新得promise对象p2,这里其实可以分解为p2.then(),那么则必须等待p2的状态确定以后才能调用后面的then,假设p2的状态为reject,则后面的then根本不会调用
  8. p1.then(function(value) {
  9. console.log(value);
  10. return p2;
  11. }).then(function(value) {
  12. console.log(value);
  13. });

Promise.all

Promise.all可以同时执行多个promise,返回一个新的promise对象。当所有的执行的promise对象为resolve时,返回的则是一个包含每个决议值( 42 、 43 与 44 )的数组,这些值的存储顺序保持了待决议的 Promise 的顺序(与完成的先后顺序无关),因此你可以将结果匹配到每个Promise。但是只要其中任何一个被reject了,会立马返回一个promise reject对象,值则会第一个被reject的返回值。

  1. let p1 = new Promise(function(resolve, reject) { resolve(42); });
  2. let p2 = new Promise(function(resolve, reject) { resolve(43); });
  3. let p3 = new Promise(function(resolve, reject) { resolve(44); });
  4. let p4 = Promise.all([p1, p2, p3]);
  5. p4.then(function(value) {
  6. console.log(Array.isArray(value)); // true
  7. console.log(value[0]); // 42
  8. console.log(value[1]); // 43
  9. console.log(value[2]); // 44
  10. });
  11. //假设p3为reject状态
  12. let p3 = new Promise(function(resolve, reject) { reject(44)});
  13. p4.catch(function(error){console.log(error)}) //44

Promise.race

此方法也接受一个包含需 监视的 Promise 的可迭代对象,并返回一个新的 Promise对象 ,但一旦来源 Promise 中有一个被 解决,所返回的 Promise 就会立刻被解决。与等待所有 Promise 完成的 Promise.all() 方法 不同,在来源 Promise 中任意一个被完成时, Promise.race() 方法所返回的 Promise 就能 作出响应。

  1. let p1 = Promise.resolve(42);
  2. let p2 = new Promise(function(resolve, reject) { resolve(43); });
  3. let p3 = new Promise(function(resolve, reject) { resolve(44); });
  4. let p4 = Promise.race([p1, p2, p3]);
  5. p4.then(function(value) { console.log(value); });// 42

Promise源码实现

源码实现参考了这两篇文章 Promise源码1,Promise源码2

Promise其实是一个构造函数,具有以下特征:

  • 构造函数Promise必须接受一个函数作为参数,我们称该函数为executorexecutor又包含resolvereject两个参数,它们是两个函数。
  • 状态只能由 pending 变为 resolved或由 pending 变为 reject,且状态改变之后不会在发生变化,会一直保持这个状态。
  • 上文中executor函数包含 resolvereject 两个参数,它们是两个函数,可以用于改变 Promise 的状态和传入 Promise 的值
  1. 实现MyPromise构造函数
  1. function MyPromise(executor){
  2. //判断传入的executor是否是函数
  3. if (typeof executor !== 'function') {
  4. throw new Error('MyPromise must accept a function as a parameter')
  5. }
  6. //默认状态是等待状态
  7. this.status = 'pending';
  8. this.value = undefined;
  9. function resolve (data){
  10. if(this.status === 'pending'){
  11. this.value = data;
  12. this.status = "resolved";
  13. }
  14. }
  15. function reject(data){
  16. if(this.status === 'pending'){
  17. this.value = data;
  18. this.status = "reject";
  19. }
  20. }
  21. //执行传入的executor,executor中会调用resolve跟reject
  22. try{
  23. executor(resolve,reject)
  24. }catch (e){
  25. reject(e);//promise失败了
  26. }
  27. }
  28. }

上述实现了Promise的构造函数,传入方法参数,执行完成后调用resolve函数,将pendding状态更改为resolved状态。或调用reject函数,将pendding状态改为reject函数。

  1. 实现then方法
    then方法传入两个参数,分别为resolve状态的回调函数跟reject状态的回调函数
  1. MyPromise.prototype.then=function(onFulFilled, onRejected) {
  2. if (this.status === 'resolved') {
  3. //成功状态的回调
  4. onFulFilled(this.value);
  5. }
  6. if (this.status === 'rejected') {
  7. //失败状态的回调
  8. onRejected(this.reason);
  9. }
  10. }

在成功的时候调用了onFulFilled方法,那么如果executor还未执行完,状态还是pending状态,此时调用then方法就无法进行任何处理了。所以这边更改上面的构造函数,加入回调事件队列,在状态改变时循环执行回调事件。当调用then方法时,如果为pending状态,我们将所有回调函数加入一个队列,在executor方法执行完执行resolve方法时,就调用这个函数队列全部执行。这样子保证了在调用then方法的时候,会执行回调函数

  1. function MyPromise(executor){
  2. if (typeof handle !== 'function') {
  3. throw new Error('MyPromise must accept a function as a parameter')
  4. }
  5. this.status = 'panding';
  6. this.value = undefined;
  7. //存放成功的回调
  8. this.onResolvedCallbacks = [];
  9. //存放失败的回调
  10. this.onRejectedCallbacks = [];
  11. function resolve (data){
  12. if(this.status === 'pending'){
  13. this.value = data;
  14. this.status = "resolved";
  15. //等状态变更,执行消息队列中的所有回调函数
  16. this.onResolvedCallbacks.forEach(fn => fn());
  17. }
  18. }
  19. function reject(data){
  20. if(this.status === 'pending'){
  21. this.value = data;
  22. this.status = "reject";
  23. this.onRejectedCallbacks.forEach(fn => fn());
  24. }
  25. }
  26. try{
  27. executor(resolve,reject)
  28. }catch (e){
  29. reject(e);
  30. }
  31. }
  32. }
  33. MyPromise.prototype.then(onFulFilled, onRejected) {
  34. if (this.status === 'resolved') {
  35. onFulFilled(this.value);
  36. }
  37. if (this.status === 'rejected') {
  38. onRejected(this.reason);
  39. }
  40. // 当前既没有完成 也没有失败
  41. if (this.status === 'pending') {
  42. // 存放成功的回调
  43. this.onResolvedCallbacks.push(() => {
  44. onFulFilled(this.value);
  45. });
  46. // 存放失败的回调
  47. this.onRejectedCallbacks.push(() => {
  48. onRejected(this.reason);
  49. });
  50. }
  51. }
  1. 实现链式调用

promise的then方法存在链式调用,每次调用then方法成功时,都会返回一个新的promise对象,并执行下一个then方法。不过内部的返回值可能存在不同的情况:

  • 具体数值x或者具体对象,那么返回一个promise对象new Promise(function(resolve,reject){resolve(x)})
  • 返回自身,p.then(function(resolve,reject){return p})那么则会无限调用,此处需要判断并报错
  • 返回一个其他的promise对象,那么我们需要等待这个promise对象的状态变更为resolved跟reject,但是可能存在循环内部都是promise。所以需要对返回的promise进行递归调用自身的then方法。

我们将返回值判断封装为一个新的方法resolvePromise。promise2代表上一个then方法的返回值,x代表传入then方法的参数。

  1. let p1=new MyPromise(function(resolve,reject){resolve(11)})
  2. let p2=p1.then(
  3. function(value){
  4. return value
  5. }
  6. )
  7. resolvePromise(p2,value,resolve,reject)
  8. function resolvePromise(promise2,x,resolve,reject){
  9. //判断x是不是promise,即返回值等于本身,造成循环引用
  10. if(promise2 === x){
  11. return reject(new TypeError('循环引用'));
  12. };
  13. // x是除了null以外的对象或者函数
  14. if(x !==null && (typeof x === 'object' || typeof x === 'function')){
  15. let called;//防止成功后调用失败
  16. try{
  17. //防止取then是出现异常 判断是否是新promise对象,如果是promise对象,则存在then方法
  18. let then = x.then;
  19. if(typeof then === 'function'){
  20. //如果是promise对象,则调用这个promise的then,因为我们在链式调用的过程中,如果返回的是pormise对象,我们需要这个promise对象进行决议以后(即状态变更为resolved跟reject我们才会继续调用下一个then方法,所以我们这边进行递归调用,一直到返回的最终的是值为止)
  21. then.call(x,y => {
  22. //如果Y是promise就继续递归promise
  23. if(called) return;
  24. called = true;
  25. resolvePromise(promise2,y,resolve,reject)
  26. },r => {
  27. //只要失败了就失败了
  28. if(called) return;
  29. called = true;
  30. reject(r);
  31. });
  32. }else{
  33. //then是一个普通对象,就直接成功即可
  34. resolve(x);
  35. }
  36. }catch (e){
  37. if(called) return;
  38. called = true;
  39. reject(e)
  40. }
  41. }else{
  42. //x = 123 x就是一个普通值 作为下个then成功的参数
  43. resolve(x)
  44. }
  45. }

这边就实现了一个简单的Promise。后续的Promise.all跟Promise.race的源码实现可以参考上述的两篇文章。

Async跟Await

async跟await是Promise的语法糖。在promise中我们可以用链式调用来解决采用回调函数的回调地狱。但是链式调用也是存在一些不方便,因为promise是异步方法,链式调用的promise跟同步方法写在一起会让人存在疑惑。async跟await可以将异步语法优化成同步语法,并且能实现then链式调用的所有功能。

用async修饰的函数返回的是一个promise对象,如果在函数中返回的是一个具体值,则会返回Promise.resolve(x)的promise对象.原理是generator状态机。

  1. async function TestAysnc(){
  2. return 'hello world'
  3. }
  4. TestAysnc() //输出Promise('hello world')
  5. //当然 返回的promise对象可以用then方法进行调用
  6. TestAysnc().then(function(value){
  7. console.log(value)
  8. })
  9. // hello world

await的函数必须用async修饰

await的作用是等待async函数返回的promise返回结果,并阻塞await后面的语句执行。 其实await可以等待任何函数,不一定是async修饰的函数,不过如果等待普通函数,其实跟同步函数写法没什么区别,也没必要用await去修饰。

语法规定,await 只能出现在 async 函数中,为何await必须与async搭配。记住最关键的一点,async是将返回结果修饰成Promise对象,await就是等待函数返回结果,阻塞下面的代码执行,等待的函数会立即执行

  1. async function test1(){
  2. console.log('test1')
  3. }
  4. async function zhixing1(){
  5. await test1()
  6. console.log('test2')
  7. }
  8. zhixing1()
  9. let a=new Promise(function(resolve,reject){
  10. console.log('test3')
  11. resolve('test3')
  12. })
  13. //输出test1 test3 test2 await阻塞了后面代码的执行 但是test1()是立即执行的,立即返回结果Promise.resolved('test2'),加入到异步队列中。

async与await主要作用是用于简化then的链式调用,并提供同步的语法,使异步操作更加直观。

  1. function doIt() {
  2. step1(time1)
  3. .then(time2 => step2(time2))
  4. .then(time3 => step3(time3))
  5. .then(result => {
  6. console.log(`result is ${result}`);
  7. });
  8. }
  9. async function doIt() {
  10. let result1=await setp1(time1)
  11. let result2=await setp2(result1)
  12. let result3=await setp3(result2)
  13. console.log(result3)
  14. }

错误处理,在promise的链式调用中我们可以抛出reject被最后catch处理,在async跟await中可以使用try-catch来捕获异常。

  1. async funtion newTest(){
  2. try{
  3. await doIt()
  4. }catch(err){
  5. console.log(err)
  6. }
  7. }

结语

这块其实有很多知识点跟面试点,promise源码实现,async跟await,宏任务微任务等等。好好的整理了一下,面试自信心提升1%。