首页IT科技js中异步(JavaScript知识总结 异步编程篇)

js中异步(JavaScript知识总结 异步编程篇)

时间2025-04-30 12:06:50分类IT科技浏览3409
导读:这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助...

这里给大家分享我在网上总结出来的一些知识           ,希望对大家有所帮助

func1().then(res => { console.log(res); // 30 })

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构造函数接受一个函数作为参数                 ,该函数的两个参数分别是resolvereject。

const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });

一般情况下都会使用new Promise()来创建promise对象      ,但是也可以使用promise.resolvepromise.reject这两个方法:

Promise.resolve

Promise.resolve(value)的返回值也是一个promise对象           ,可以对返回值进行.then调用                 ,代码如下:

Promise.resolve(11).then(function(value){ console.log(value); // 打印出11 });

resolve(11)代码中      ,会让promise对象进入确定(resolve状态)     ,并将参数11传递给后面的then所指定的onFulfilled 函数;

创建promise对象可以使用new Promise的形式创建对象                 ,也可以使用Promise.resolve(value)的形式创建promise对象;

Promise.reject

Promise.reject 也是new Promise的快捷形式           ,也创建一个promise对象                 。代码如下

Promise.reject(new Error(“我错了     ,请原谅俺!!                 ”));

就是下面的代码new Promise的简单形式:

new Promise(function(resolve,reject){ reject(new Error("我错了                 ,请原谅俺!!")); });

下面是使用resolve方法和reject方法:

function testPromise(ready) { return new Promise(function(resolve,reject){ if(ready) { resolve("hello world"); }else { reject("No thanks"); } }); }; // 方法调用 testPromise(true).then(function(msg){ console.log(msg); },function(error){ console.log(error); });

上面的代码的含义是给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创建完了      ,那该如何调用呢?

promise.then(function(value) { // success }, function(error) { // failure });

then方法可以接受两个回调函数作为参数                 。第一个回调函数是Promise对象的状态变为resolved时调用     ,第二个回调函数是Promise对象的状态变为rejected时调用      。其中第二个参数可以省略           。

then方法返回的是一个新的Promise实例(不是原来那个Promise实例)                 。因此可以采用链式写法                 ,即then方法后面再调用另一个then方法      。

当要写有顺序的异步事件时           ,需要串行时     ,可以这样写:

let promise = new Promise((resolve,reject)=>{ ajax(first).success(function(res){ resolve(res); }) }) promise.then(res=>{ return new Promise((resovle,reject)=>{ ajax(second).success(function(res){ resolve(res) }) }) }).then(res=>{ return new Promise((resovle,reject)=>{ ajax(second).success(function(res){ resolve(res) }) }) }).then(res=>{ })

那当要写的事件没有顺序或者关系时                 ,还如何写呢?可以使用all 方法来解决     。

2. catch()

Promise对象除了有then方法           ,还有一个catch方法,该方法相当于then方法的第二个参数                 ,指向reject的回调函数                 。不过catch方法还有一个作用                 ,就是在执行resolve回调函数时,如果出现错误           ,抛出异常                 ,不会停止运行      ,而是进入catch方法中           。

p.then((data) => { console.log(resolved,data); },(err) => { console.log(rejected,err); } ); p.then((data) => { console.log(resolved,data); }).catch((err) => { console.log(rejected,err); });

3. all()

all方法可以完成并行任务           , 它接收一个数组                 ,数组的每一项都是一个promise对象     。当数组中所有的promise的状态都达到resolved的时候      ,all方法的状态就会变成resolved     ,如果有一个状态变成了rejected                 ,那么all方法的状态就会变成rejected                 。

javascript let promise1 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(1); },2000) }); let promise2 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(2); },1000) }); let promise3 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(3); },3000) }); Promise.all([promise1,promise2,promise3]).then(res=>{ console.log(res); //结果为:[1,2,3] })

调用all方法时的结果成功的时候是回调函数的参数也是一个数组           ,这个数组按顺序保存着每一个promise对象resolve执行时的值           。

(4)race()

race方法和all一样     ,接受的参数是一个每项都是promise的数组                 ,但是与all不同的是           ,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved                 ,那自身的状态变成了resolved;反之第一个promise变成rejected                 ,那自身状态就会变成rejected                 。

let promise1 = new Promise((resolve,reject)=>{ setTimeout(()=>{ reject(1); },2000) }); let promise2 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(2); },1000) }); let promise3 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(3); },3000) }); Promise.race([promise1,promise2,promise3]).then(res=>{ console.log(res); //结果:2 },rej=>{ console.log(rej)}; )

那么race方法有什么实际作用呢?当要做一件事,超过多长时间就不做了           ,可以用这个方法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

5. finally()

finally方法用于指定不管 Promise 对象最后状态如何                 ,都会执行的操作                 。该方法是 ES2018 引入标准的。

promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});

上面代码中      ,不管promise最后的状态           ,在执行完thencatch指定的回调函数以后                 ,都会执行finally方法指定的回调函数           。

下面是一个例子      ,服务器使用 Promise 处理请求     ,然后使用finally方法关掉服务器                 。

server.listen(port) .then(function () { // ... }) .finally(server.stop);

finally方法的回调函数不接受任何参数                 ,这意味着没有办法知道           ,前面的 Promise 状态到底是fulfilled还是rejected      。这表明     ,finally方法里面的操作                 ,应该是与状态无关的           ,不依赖于 Promise 的执行结果           。finally本质上是then方法的特例:

promise .finally(() => { // 语句 }); // 等同于 promise .then( result => { // 语句 return result; }, error => { // 语句 throw error; } );

上面代码中,如果不使用finally方法                 ,同样的语句需要为成功和失败两种情况各写一次                 。有了finally方法                 ,则只需要写一次      。

5. Promise解决了什么问题

在工作中经常会碰到这样一个需求,比如我使用ajax发一个A请求后           ,成功后拿到数据                 ,需要把数据传给B请求;那么需要如下编写代码:

let fs = require(fs) fs.readFile(./a.txt,utf8,function(err,data){ fs.readFile(data,utf8,function(err,data){ fs.readFile(data,utf8,function(err,data){ console.log(data) }) }) })

上面的代码有如下缺点:

后一个请求需要依赖于前一个请求成功后      ,将数据往下传递           ,会导致多个ajax请求嵌套的情况                 ,代码不够直观     。 如果前后两个请求不需要传递参数的情况下      ,那么后一个请求也需要前一个请求成功后再执行下一步操作     ,这种情况下                 ,那么也需要如上编写代码           ,导致代码不够直观                 。

Promise出现之后     ,代码变成这样:

let fs = require(fs) function read(url){ return new Promise((resolve,reject)=>{ fs.readFile(url,utf8,function(error,data){ error && reject(error) resolve(data) }) }) } read(./a.txt).then(data=>{ return read(data) }).then(data=>{ return read(data) }).then(data=>{ console.log(data) })

这样代码看起了就简洁了很多                 ,解决了地狱回调的问题           。

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])里面哪个结果获得的快     ,就返回那个结果                 ,不管结果本身是成功状态还是失败状态                 。当要做一件事           ,超过多长时间就不做了,可以用这个方法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

7. 对async/await 的理解

async/await其实是Generator 的语法糖                 ,它能实现的效果都能用then链来实现                 ,它是为优化then链而开发出来的                 。从字面上来看,async是“异步           ”的简写           ,await则为等待                 ,所以很好理解async 用于申明一个 function 是异步的      ,而 await 用于等待一个异步方法执行完成。当然语法上强制规定await只能出现在asnyc函数中           ,先来看看async函数返回了什么:

async function testAsy(){ return hello world; } let result = testAsy(); console.log(result)

所以                 ,async 函数返回的是一个 Promise 对象           。async 函数(包含函数语句                 、函数表达式           、Lambda表达式)会返回一个 Promise 对象      ,如果在函数中 return 一个直接量     ,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象                 。

async 函数返回的是一个 Promise 对象                 ,所以在最外层不能用 await 获取其返回值的情况下           ,当然应该用原来的方式:then() 链来处理这个 Promise 对象     ,就像这样:

async function testAsy(){ return hello world } let result = testAsy() console.log(result) result.then(v=>{ console.log(v) // hello world })

那如果 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 后面实际是可以接普通函数调用或者直接量的     。所以下面这个示例完全可以正确运行:

function getSomething() { return "something"; } async function testAsync() { return Promise.resolve("hello async"); } async function test() { const v1 = await getSomething(); const v2 = await testAsync(); console.log(v1, v2); } test();

await 表达式的运算结果取决于它等的是什么                 。

如果它等到的不是一个 Promise 对象                 ,那 await 表达式的运算结果就是它等到的东西           。 如果它等到的是一个 Promise 对象                 ,await 就忙起来了,它会阻塞后面的代码           ,等着 Promise 对象 resolve                 ,然后得到 resolve 的值      ,作为 await 表达式的运算结果。

来看一个例子:

function testAsy(x){ return new Promise(resolve=>{setTimeout(() => { resolve(x); }, 3000) } ) } async function testAwt(){ let result = await testAsy(hello world); console.log(result); // 3秒钟之后出现hello world console.log(cuger) // 3秒钟之后出现cug } testAwt(); console.log(cug) //立即输出cug

这就是 await 必须用在 async 函数中的原因                 。async 函数调用不会造成阻塞           ,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行                 。await暂停当前async的执行                 ,所以cug最先输出      ,hello world和‘cuger’是3秒钟后同时出现的。

9. async/await的优势

单一的 Promise 链并不能发现 async/await 的优势     ,但是                 ,如果需要处理由多个 Promise 组成的 then 链的时候           ,优势就能体现出来了(很有意思     ,Promise 通过 then 链来解决多层回调的问题                 ,现在又用 async/await 来进一步优化它)           。

假设一个业务           ,分多个步骤完成,每个步骤都是异步的                 ,而且依赖于上一个步骤的结果                 。仍然用 setTimeout 来模拟异步操作:

/** * 传入参数 n                 ,表示这个函数执行的时间(毫秒) * 执行的结果是 n + 200,这个值将用于下一步骤 */ function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n); }); } function step1(n) { console.log(`step1 with ${n}`); return takeLongTime(n); } function step2(n) { console.log(`step2 with ${n}`); return takeLongTime(n); } function step3(n) { console.log(`step3 with ${n}`); return takeLongTime(n); }

现在用 Promise 方式来实现这三个步骤的处理:

function doIt() { console.time("doIt"); const time1 = 300; step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(`result is ${result}`); console.timeEnd("doIt"); }); } doIt(); // c:\var\test>node --harmony_async_await . // step1 with 300 // step2 with 500 // step3 with 700 // result is 900 // doIt: 1507.251ms

输出结果 resultstep3() 的参数 700 + 200 = 900      。doIt() 顺序执行了三个步骤           ,一共用了 300 + 500 + 700 = 1500 毫秒                 ,和 console.time()/console.timeEnd() 计算的结果一致           。

如果用 async/await 来实现呢      ,会是这样:

async function doIt() { console.time("doIt"); const time1 = 300; const time2 = await step1(time1); const time3 = await step2(time2); const result = await step3(time3); console.log(`result is ${result}`); console.timeEnd("doIt"); } doIt();

结果和之前的 Promise 实现是一样的           ,但是这个代码看起来是不是清晰得多                 ,几乎跟同步代码一样

10. async/await对比Promise的优势

代码读起来更加同步      ,Promise虽然摆脱了回调地狱     ,但是then的链式调⽤也会带来额外的阅读负担 Promise传递中间值⾮常麻烦                 ,⽽async/await⼏乎是同步的写法           ,⾮常优雅 错误处理友好     ,async/await可以⽤成熟的try/catch                 ,Promise的错误捕获⾮常冗余 调试友好           ,Promise的调试很差,由于没有代码块                 ,你不能在⼀个返回表达式的箭头函数中设置断点                 ,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块           ,因为调试器只能跟踪同步代码的每⼀步                 。

11. async/await 如何捕获异常

async function fn(){ try{ let a = await Promise.reject(error) }catch(error){ console.log(error) } }

12. 并发与并行的区别?

并发是宏观概念                 ,我分别有任务 A 和任务 B      ,在一段时间内通过任务间的切换完成了这两个任务           ,这种情况就可以称之为并发      。 并行是微观概念                 ,假设 CPU 中存在两个核心      ,那么我就可以同时完成任务 A、B     。同时完成多个任务的情况就可以称之为并行                 。

13. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?

以下代码就是一个回调函数的例子:

ajax(url, () => { // 处理逻辑 })

回调函数有一个致命的弱点     ,就是容易写出回调地狱(Callback hell)           。假设多个请求存在依赖性                 ,可能会有如下代码:

ajax(url, () => { // 处理逻辑 ajax(url1, () => { // 处理逻辑 ajax(url2, () => { // 处理逻辑 }) }) })

以上代码看起来不利于阅读和维护           ,当然     ,也可以把函数分开来写:

function firstAjax() { ajax(url1, () => { // 处理逻辑 secondAjax() }) } function secondAjax() { ajax(url2, () => { // 处理逻辑 }) } ajax(url, () => { // 处理逻辑 firstAjax() })

以上的代码虽然看上去利于阅读了                 ,但是还是没有解决根本问题     。回调地狱的根本问题就是:

嵌套函数存在耦合性           ,一旦有所改动,就会牵一发而动全身 嵌套函数一多                 ,就很难处理错误

当然                 ,回调函数还存在着别的几个缺点,比如不能使用 try catch 捕获错误           ,不能直接 return                 。

14. setTimeout                 、setInterval                 、requestAnimationFrame 各有什么特点?

异步编程当然少不了定时器了                 ,常见的定时器函数有 setTimeout、setInterval           、requestAnimationFrame           。最常用的是setTimeout      ,很多人认为 setTimeout 是延时多久           ,那就应该是多久后执行。

其实这个观点是错误的                 ,因为 JS 是单线程执行的      ,如果前面的代码影响了性能     ,就会导致 setTimeout 不会按期执行                 。当然了                 ,可以通过代码去修正 setTimeout           ,从而使定时器相对准确:

let period = 60 * 1000 * 60 * 2 let startTime = new Date().getTime() let count = 0 let end = new Date().getTime() + period let interval = 1000 let currentInterval = interval function loop() { count++ // 代码执行所消耗的时间 let offset = new Date().getTime() - (startTime + count * interval); let diff = end - new Date().getTime() let h = Math.floor(diff / (60 * 1000 * 60)) let hdiff = diff % (60 * 1000 * 60) let m = Math.floor(hdiff / (60 * 1000)) let mdiff = hdiff % (60 * 1000) let s = mdiff / (1000) let sCeil = Math.ceil(s) let sFloor = Math.floor(s) // 得到下一次循环所消耗的时间 currentInterval = interval - offset console.log(时:+h, 分:+m, 毫秒:+s, 秒向上取整:+sCeil, 代码执行时间:+offset, 下次循环间隔+currentInterval) setTimeout(loop, currentInterval) } setTimeout(loop, currentInterval)

接下来看 setInterval     ,其实这个函数作用和 setTimeout 基本一致                 ,只是该函数是每隔一段时间执行一次回调函数                 。

通常来说不建议使用 setInterval。第一           ,它和 setTimeout 一样,不能保证在预期的时间执行任务           。第二                 ,它存在执行累积的问题                 ,请看以下伪代码

function demo() { setInterval(function(){ console.log(2) },1000) sleep(2000) } demo()

以上代码在浏览器环境中,如果定时器执行过程中出现了耗时操作           ,多个回调函数会在耗时操作结束以后同时执行                 ,这样可能就会带来性能上的问题                 。

如果有循环定时器的需求      ,其实完全可以通过 requestAnimationFrame 来实现:

function setInterval(callback, interval) { let timer const now = Date.now let startTime = now() let endTime = startTime const loop = () => { timer = window.requestAnimationFrame(loop) endTime = now() if (endTime - startTime >= interval) { startTime = endTime = now() callback(timer) } } timer = window.requestAnimationFrame(loop) return timer } let a = 0 setInterval(timer => { console.log(1) a++ if (a === 3) cancelAnimationFrame(timer) }, 1000)

首先 requestAnimationFrame 自带函数节流功能           ,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下)                 ,并且该函数的延时效果是精确的      ,没有其他定时器时间不准的问题     ,当然你也可以通过该函数来实现 setTimeout      。

如果对您有所帮助                 ,欢迎您点个关注           ,我会定时更新技术文档     ,大家一起讨论学习                 ,一起进步           。

声明:本站所有文章           ,如无特殊说明或标注,均为本站原创发布                 。任何个人或组织                 ,在未征得本站同意时                 ,禁止复制                 、盗用      、采集           、发布本站内容到任何网站                、书籍等各类媒体平台      。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理     。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
vue移动端左右滑动(Vue实战篇三十五:实现滑动拼图验证登录) 2018网页游戏赚钱(有什么网页游戏赚钱吗-是兄弟就来砍我,贪玩蓝月传奇类游戏年收入十亿大揭秘)