js中异步(JavaScript知识总结 异步编程篇)
这里给大家分享我在网上总结出来的一些知识 ,希望对大家有所帮助
await的含义为等待 ,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后 ,才能继续执行下面的代码 。await通过返回一个Promise对象来实现同步的效果 。
3. 对Promise的理解
Promise是异步编程的一种解决方案 ,它是一个对象 ,可以获取异步操作的消息 ,他的出现大大改善了异步编程的困境 ,避免了地狱回调 ,它比传统的解决方案回调函数和事件更合理和更强大 。
所谓Promise ,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果 。从语法上说 ,Promise 是一个对象 ,从它可以获取异步操作的消息 。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理 。
(1)Promise的实例有三个状态:
Pending(进行中) Resolved(已完成) Rejected(已拒绝)当把一件事情交给promise时 ,它的状态就是Pending ,任务完成了状态就变成了Resolved 、没有完成失败了就变成了Rejected 。
(2)Promise的实例有两个过程:
pending -> fulfilled : Resolved(已完成) pending -> rejected:Rejected(已拒绝)注意:一旦从进行状态变成为其他状态就永远不能更改状态了 。
Promise的特点:
对象的状态不受外界影响 。promise对象代表一个异步操作,有三种状态 ,pending(进行中) 、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果 ,可以决定当前是哪一种状态 ,任何其他操作都无法改变这个状态 ,这也是promise这个名字的由来——“承诺 ”; 一旦状态改变就不会再变 ,任何时候都可以得到这个结果 。promise对象的状态改变 ,只有两种可能:从pending变为fulfilled ,从pending变为rejected 。这时就称为resolved(已定型)。如果改变已经发生了 ,你再对promise对象添加回调函数 ,也会立即得到这个结果 。这与事件(event)完全不同,事件的特点是:如果你错过了它 ,再去监听是得不到结果的 。Promise的缺点:
无法取消Promise ,一旦新建它就会立即执行,无法中途取消。 如果不设置回调函数 ,Promise内部抛出的错误 ,不会反应到外部 。 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成) 。总结:
Promise 对象是异步编程的一种解决方案 ,最早由社区提出 。Promise 是一个构造函数 ,接收一个函数作为参数 ,返回一个 Promise 实例 。一个 Promise 实例有三种状态 ,分别是pending 、resolved 和 rejected ,分别代表了进行中 、已成功和已失败 。实例的状态只能由 pending 转变 resolved 或者rejected 状态 ,并且状态一经改变 ,就凝固了 ,无法再被改变了 。
状态的改变是通过 resolve() 和 reject() 函数来实现的 ,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法 ,使用这个 then 方法可以为两个状态的改变注册回调函数 。这个回调函数属于微任务 ,会在本轮事件循环的末尾执行 。
注意:在构造 Promise 的时候,构造函数内部的代码是立即执行的
4. Promise的基本用法
(1)创建Promise对象Promise对象代表一个异步操作 ,有三种状态:pending(进行中) 、fulfilled(已成功)和rejected(已失败) 。
Promise构造函数接受一个函数作为参数 ,该函数的两个参数分别是resolve和reject。
一般情况下都会使用new Promise()来创建promise对象,但是也可以使用promise.resolve和 promise.reject这两个方法:
Promise.resolvePromise.resolve(value)的返回值也是一个promise对象 ,可以对返回值进行.then调用 ,代码如下:
resolve(11)代码中 ,会让promise对象进入确定(resolve状态) ,并将参数11传递给后面的then所指定的onFulfilled 函数;
创建promise对象可以使用new Promise的形式创建对象 ,也可以使用Promise.resolve(value)的形式创建promise对象;
Promise.rejectPromise.reject 也是new Promise的快捷形式 ,也创建一个promise对象 。代码如下:
就是下面的代码new Promise的简单形式:
下面是使用resolve方法和reject方法:
上面的代码的含义是给testPromise方法传递一个参数 ,返回一个promise对象,如果为true的话 ,那么调用promise对象中的resolve()方法 ,并且把其中的参数传递给后面的then第一个函数内,因此打印出 “hello world”, 如果为false的话 ,会调用promise对象中的reject()方法 ,则会进入then的第二个函数内,会打印No thanks;
(2)Promise方法Promise有五个常用的方法:then() 、catch() 、all() 、race() 、finally 。下面就来看一下这些方法。
then()当Promise执行的内容符合成功条件时 ,调用resolve函数 ,失败就调用reject函数 。Promise创建完了 ,那该如何调用呢?
then方法可以接受两个回调函数作为参数 。第一个回调函数是Promise对象的状态变为resolved时调用 ,第二个回调函数是Promise对象的状态变为rejected时调用。其中第二个参数可以省略 。
then方法返回的是一个新的Promise实例(不是原来那个Promise实例) 。因此可以采用链式写法 ,即then方法后面再调用另一个then方法 。
当要写有顺序的异步事件时 ,需要串行时 ,可以这样写:
那当要写的事件没有顺序或者关系时 ,还如何写呢?可以使用all 方法来解决 。
2. catch()
Promise对象除了有then方法 ,还有一个catch方法,该方法相当于then方法的第二个参数 ,指向reject的回调函数 。不过catch方法还有一个作用 ,就是在执行resolve回调函数时,如果出现错误 ,抛出异常 ,不会停止运行,而是进入catch方法中 。
3. all()
all方法可以完成并行任务 , 它接收一个数组 ,数组的每一项都是一个promise对象 。当数组中所有的promise的状态都达到resolved的时候 ,all方法的状态就会变成resolved ,如果有一个状态变成了rejected ,那么all方法的状态就会变成rejected 。
调用all方法时的结果成功的时候是回调函数的参数也是一个数组 ,这个数组按顺序保存着每一个promise对象resolve执行时的值 。
(4)race()
race方法和all一样 ,接受的参数是一个每项都是promise的数组 ,但是与all不同的是 ,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved ,那自身的状态变成了resolved;反之第一个promise变成rejected ,那自身状态就会变成rejected 。
那么race方法有什么实际作用呢?当要做一件事,超过多长时间就不做了 ,可以用这个方法来解决:
5. finally()
finally方法用于指定不管 Promise 对象最后状态如何 ,都会执行的操作 。该方法是 ES2018 引入标准的。
上面代码中,不管promise最后的状态 ,在执行完then或catch指定的回调函数以后 ,都会执行finally方法指定的回调函数 。
下面是一个例子 ,服务器使用 Promise 处理请求 ,然后使用finally方法关掉服务器 。
finally方法的回调函数不接受任何参数 ,这意味着没有办法知道 ,前面的 Promise 状态到底是fulfilled还是rejected。这表明 ,finally方法里面的操作 ,应该是与状态无关的 ,不依赖于 Promise 的执行结果 。finally本质上是then方法的特例:
上面代码中,如果不使用finally方法 ,同样的语句需要为成功和失败两种情况各写一次 。有了finally方法 ,则只需要写一次 。
5. Promise解决了什么问题
在工作中经常会碰到这样一个需求,比如我使用ajax发一个A请求后 ,成功后拿到数据 ,需要把数据传给B请求;那么需要如下编写代码:
上面的代码有如下缺点:
后一个请求需要依赖于前一个请求成功后,将数据往下传递 ,会导致多个ajax请求嵌套的情况 ,代码不够直观 。 如果前后两个请求不需要传递参数的情况下 ,那么后一个请求也需要前一个请求成功后再执行下一步操作 ,这种情况下 ,那么也需要如上编写代码 ,导致代码不够直观 。Promise出现之后 ,代码变成这样:
这样代码看起了就简洁了很多 ,解决了地狱回调的问题 。
6. Promise.all和Promise.race的区别的使用场景
(1)Promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例 。同时 ,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组 ,而失败的时候则返回最先被reject失败状态的值 。
Promise.all中传入的是数组 ,返回的也是是数组,并且会将进行映射 ,传入的promise对象返回的值是按照顺序在数组中排列的 ,但是注意的是他们执行的顺序并不是按照顺序的,除非可迭代对象为空 。
需要注意 ,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的 ,这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景 ,就可以使用Promise.all来解决。
(2)Promise.race
顾名思义 ,Promse.race就是赛跑的意思 ,意思就是说 ,Promise.race([p1, p2, p3])里面哪个结果获得的快 ,就返回那个结果 ,不管结果本身是成功状态还是失败状态 。当要做一件事 ,超过多长时间就不做了,可以用这个方法来解决:
7. 对async/await 的理解
async/await其实是Generator 的语法糖 ,它能实现的效果都能用then链来实现 ,它是为优化then链而开发出来的 。从字面上来看,async是“异步 ”的简写 ,await则为等待 ,所以很好理解async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。当然语法上强制规定await只能出现在asnyc函数中 ,先来看看async函数返回了什么:
所以 ,async 函数返回的是一个 Promise 对象 。async 函数(包含函数语句 、函数表达式 、Lambda表达式)会返回一个 Promise 对象 ,如果在函数中 return 一个直接量 ,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象 。
async 函数返回的是一个 Promise 对象 ,所以在最外层不能用 await 获取其返回值的情况下 ,当然应该用原来的方式:then() 链来处理这个 Promise 对象 ,就像这样:
那如果 async 函数没有返回值 ,又该如何?很容易想到 ,它会返回 Promise.resolve(undefined)。
联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数 ,它会立即执行 ,返回一个 Promise 对象,并且 ,绝不会阻塞后面的语句 。这和普通返回 Promise 对象的函数并无二致 。
注意:Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写 ,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例 。
8. await 到底在等啥?
await 在等待什么呢?一般来说 ,都认为 await 是在等待一个 async 函数完成 。不过按语法说明 ,await 等待的是一个表达式 ,这个表达式的计算结果是 Promise 对象或者其它值(换句话说 ,就是没有特殊限定) 。
因为 async 函数返回一个 Promise 对象 ,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数 ,但要清楚 ,它等的实际是一个返回值 。注意到 await 不仅仅用于等 Promise 对象 ,它可以等任意表达式的结果 ,所以,await 后面实际是可以接普通函数调用或者直接量的 。所以下面这个示例完全可以正确运行:
await 表达式的运算结果取决于它等的是什么 。
如果它等到的不是一个 Promise 对象 ,那 await 表达式的运算结果就是它等到的东西 。 如果它等到的是一个 Promise 对象 ,await 就忙起来了,它会阻塞后面的代码 ,等着 Promise 对象 resolve ,然后得到 resolve 的值,作为 await 表达式的运算结果。来看一个例子:
这就是 await 必须用在 async 函数中的原因 。async 函数调用不会造成阻塞 ,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行 。await暂停当前async的执行 ,所以cug最先输出 ,hello world和‘cuger’是3秒钟后同时出现的。
9. async/await的优势
单一的 Promise 链并不能发现 async/await 的优势 ,但是 ,如果需要处理由多个 Promise 组成的 then 链的时候 ,优势就能体现出来了(很有意思 ,Promise 通过 then 链来解决多层回调的问题 ,现在又用 async/await 来进一步优化它) 。
假设一个业务 ,分多个步骤完成,每个步骤都是异步的 ,而且依赖于上一个步骤的结果 。仍然用 setTimeout 来模拟异步操作:
现在用 Promise 方式来实现这三个步骤的处理:
输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤 ,一共用了 300 + 500 + 700 = 1500 毫秒 ,和 console.time()/console.timeEnd() 计算的结果一致 。
如果用 async/await 来实现呢,会是这样:
结果和之前的 Promise 实现是一样的 ,但是这个代码看起来是不是清晰得多 ,几乎跟同步代码一样
10. async/await对比Promise的优势
代码读起来更加同步 ,Promise虽然摆脱了回调地狱 ,但是then的链式调⽤也会带来额外的阅读负担 Promise传递中间值⾮常麻烦 ,⽽async/await⼏乎是同步的写法 ,⾮常优雅 错误处理友好 ,async/await可以⽤成熟的try/catch ,Promise的错误捕获⾮常冗余 调试友好 ,Promise的调试很差,由于没有代码块 ,你不能在⼀个返回表达式的箭头函数中设置断点 ,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块 ,因为调试器只能跟踪同步代码的每⼀步 。11. async/await 如何捕获异常
12. 并发与并行的区别?
并发是宏观概念 ,我分别有任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务 ,这种情况就可以称之为并发 。 并行是微观概念 ,假设 CPU 中存在两个核心 ,那么我就可以同时完成任务 A、B 。同时完成多个任务的情况就可以称之为并行 。13. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?
以下代码就是一个回调函数的例子:
回调函数有一个致命的弱点 ,就是容易写出回调地狱(Callback hell) 。假设多个请求存在依赖性 ,可能会有如下代码:
以上代码看起来不利于阅读和维护 ,当然 ,也可以把函数分开来写:
以上的代码虽然看上去利于阅读了 ,但是还是没有解决根本问题 。回调地狱的根本问题就是:
嵌套函数存在耦合性 ,一旦有所改动,就会牵一发而动全身 嵌套函数一多 ,就很难处理错误当然 ,回调函数还存在着别的几个缺点,比如不能使用 try catch 捕获错误 ,不能直接 return 。
14. setTimeout 、setInterval 、requestAnimationFrame 各有什么特点?
异步编程当然少不了定时器了 ,常见的定时器函数有 setTimeout、setInterval 、requestAnimationFrame 。最常用的是setTimeout,很多人认为 setTimeout 是延时多久 ,那就应该是多久后执行。
其实这个观点是错误的 ,因为 JS 是单线程执行的 ,如果前面的代码影响了性能 ,就会导致 setTimeout 不会按期执行 。当然了 ,可以通过代码去修正 setTimeout ,从而使定时器相对准确:
接下来看 setInterval ,其实这个函数作用和 setTimeout 基本一致 ,只是该函数是每隔一段时间执行一次回调函数 。
通常来说不建议使用 setInterval。第一 ,它和 setTimeout 一样,不能保证在预期的时间执行任务 。第二 ,它存在执行累积的问题 ,请看以下伪代码
以上代码在浏览器环境中,如果定时器执行过程中出现了耗时操作 ,多个回调函数会在耗时操作结束以后同时执行 ,这样可能就会带来性能上的问题 。
如果有循环定时器的需求,其实完全可以通过 requestAnimationFrame 来实现:
首先 requestAnimationFrame 自带函数节流功能 ,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下) ,并且该函数的延时效果是精确的 ,没有其他定时器时间不准的问题 ,当然你也可以通过该函数来实现 setTimeout。
如果对您有所帮助 ,欢迎您点个关注 ,我会定时更新技术文档 ,大家一起讨论学习 ,一起进步 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!