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

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

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

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

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
word文档不显示图片怎么办(word文档几种不显示图片的解决方法) 火车头采集器是干嘛的(火车头采集discuz)