大学生涯 vue Vue学习笔记(八) Jie 2024-10-29 2024-11-20 一. Promise 1. 异步 异步:则是将耗时很长的 A 交付的工作交给系统之后,就去继续做 B 交付的工作,等到系统完成了前面的工作之后,再通过回调或者事件,继续做 A 剩下的工作。AB 工作的完成顺序,和交付他们的时间顺序无关,所以叫“异步”。
2. 回调函数 当一个函数作为参数传入另一个函数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数。我们熟悉的定时器和 Ajax 中就存在有回调函数.
1 2 3 4 5 <script > setTimeout (() => { console .log (`执行了回调函数` ); }, 3000 ); </script >
这里的回调函数是() => {console.log( 执行了回调函数 )},在满足时间 3 秒后执行。
3. 异步函数 1 2 3 4 5 6 <script > setTimeout (() => { console .log (`执行了回调函数` ); }, 3000 ); console .log (`111` ); </script >
如果按照代码编写的顺序,应该先输出“执行了回调函数”,再输出“111”。但实际输出为
111 执行了回调函数
这种不阻塞后面任务执行的任务就叫做异步任务。
4. 地狱回调 根据前面可以得出一个结论:存在异步任务的代码,不能保证能按照顺序执行,那如果非要代码顺序执行呢? 比如要说一句话,语序必须是下面这样的:武林要以和为贵,要讲武德,不要搞窝里斗。 必须要这样操作,才能保证顺序正确:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script > setTimeout (() => { console .log (`武林要以和为贵` ); setTimeout (() => { console .log (`要讲武德` ); setTimeout (() => { console .log (`不要搞窝里斗` ); }, 1000 ); }, 2000 ); }, 3000 ); </script >
可以看到,代码中的回调函数套回调函数 ,套了 3 层,这种回调函数中嵌套回调函数的情况就叫做回调地狱。当异步操作想要有顺序时,只能在一个异步成功以后的回调函数里面嵌套另一个异步的操作,如果嵌套的层数过多就形成了回调地狱 回调地狱就是为是实现代码顺序执行 而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。 那该如何解决回调地狱呢?使用 Promise 对象
5. 什么是 Promise Promise 是 ES6 异步编程的一种解决方案(目前最先进的解决方案是 async 和 await 的搭配(ES8),但是它们是基于 promise 的),从语法上讲,Promise 是一个对象或者说是构造函数,用来封装异步操作并可以获取其成功或失败的结果
6. 为什么要使用 Promise 最重要也是最主要的一个场景就是 ajax 和 axios 请求。通俗来说,由于网速的不同,可能得到返回值的时间也是不同的,但是下一步要执行的代码依赖于上一次请求返回值,这个时候就需要等待,结果出来了之后才知道怎么样继续下去.
7. Promise 特点 Promise 是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外) Promise 并未剥夺函数 return 的能力,因此无需层层传递 callback,进行回调获取数据 Promise 代码风格,容易理解,便于维护 Promise 多个异步等待合并便于解决 8. 创建 Promise 8.1 通过 Promise 构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <script > const myPromise = new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve (`成功` ); } else { reject (`失败` ); } }, 3000 ); }); console .log (`output->myPromise` , myPromise); myPromise .then ((res ) => { console .log (`output->res` , res); console .log (`output->myPromise` , myPromise); }) .catch ((err ) => { console .log (`output->err` , err); console .log (`output->myPromise` , myPromise); }); </script >
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。它们是两个函数,由 JavaScript 引擎提供,不用自己传值。
resolve 作用:将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; reject 作用:将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。 promise 对象的状态不受外界影响 (3 种状态): 1. pending[待定]初始状态 2. fulfilled[实现]操作成功 3. rejected[被否决]操作失败 当 promise 状态发生改变,就会触发 then()里的响应函数处理后续步骤;promise 状态一经改变,不会再变。 一旦状态改变就不会再变 (两种状态改变:成功或失败):从 pending 变为 fulfilled(成功) 从 pending 变为 rejected(失败) 这两种情况只要发生,状态就凝固了,不会再变了。 获取用户信息案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <script > const getUserInfo = (userId ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { const user = { id : userId, name : "jack" , age : 30 }; resolve (user); } else { reject ("用户不存在" ); } }, 4000 ); }); }; getUserInfo ("001" ) .then ((res ) => { console .log (`output->获取用户成功,用户信息为:` , res); }) .catch ((err ) => { console .log (`output->err` , err); }); </script >
在该案例中,使用 Promise 手动管理异步操作。在 getUserInfo 函数中创建了一个 Promise 对象,将异步操作封装在其中,当异步操作执行成功时,使用 resolve 方法将 Promise 状态变为成功态,并传递用户信息,当异步操作执行失败时,使用 reject 方法将 Promise 状态变为失败态,并传递错误信息。使用 then 方法和 catch 方法分别处理 Promise 的状态变化,如果 Promise 状态变为成功态,将打印用户信息,如果 Promise 状态变为失败态,将打印错误信息。
8.2 通过静态方法创建 Promise 对象 此方式也称为 Promise 的自动化管理方式。比如,Promise.resolve()可以创建一个状态为成功的 Promise 对象,Promise.reject()可以创建一个状态为失败的 Promise 对象。
案例 1: 1 2 3 4 5 6 7 8 9 10 11 <script > const myPromise1 = Promise .resolve (`成功` ); const myPromise2 = Promise .reject (`失败` ); myPromise1.then ((res ) => { console .log (`output->res` , res); }); myPromise2.catch ((err ) => { console .log (`output->err` , err); }); </script >
案例 2: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <script > const asyncFn = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve (`success` ); } else { reject (`fail` ); } }, 2000 ); }); }; Promise .resolve (asyncFn ()) .then ((value ) => { console .log (`异步操作执行成功` , value); }) .catch ((error ) => { console .log (`异步操作执行失败` , error); }); </script >
在这个例子中,定义了一个 asyncFn 函数,该函数返回一个 Promise 对象,在 Promise 对象的构造函数中使用 setTimeout 模拟了一个异步操作。然后我们使用 Promise.resolve()方法将异步操作函数的返回值转换成一个自动管理状态的 Promise 对象。最后,在使用 Promise.resolve()方法返回的 Promise 对象上使用 then()方法和 catch()方法处理异步操作成功或失败的情况。
使用 Promise.resolve()方法的好处在于,如果被传入的参数本来就是一个 Promise 对象,那么直接返回这个 Promise 对象,如果不是 Promise 对象,会自动转换成 Promise 对象,方便在异步操作逻辑中使用。
8.3 Promise 对象的链式操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 <script > const myPromise1 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise1成功" ); } else { reject ("myPromise1失败" ); } }, 2000 ); }); }; const myPromise2 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise2成功" ); } else { reject ("myPromise2失败" ); } }, 2000 ); }); }; const myPromise3 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise3成功" ); } else { reject ("myPromise3失败" ); } }, 2000 ); }); }; myPromise1 () .then ((res1 ) => { console .log (`output->res1` , res1); return myPromise2 (); }) .catch ((err1 ) => { console .log (`output->err1` , err1); return myPromise2 (); }) .then ((res2 ) => { console .log (`output->res2` , res2); return myPromise3 (); }) .catch ((err2 ) => { console .log (`output->err2` , err2); return myPromise3 (); }) .then ((res3 ) => { console .log (`output->res3` , res3); }) .catch ((err3 ) => { console .log (`output->err3` , err3); }); myPromise1 () .then ((res1 ) => { console .log (`output->res1` , res1); return myPromise2 (); }) .then ((res2 ) => { console .log (`output->res2` , res2); return myPromise3 (); }) .then ((res3 ) => { console .log (`output->res3` , res3); }) .catch ((err ) => { console .log (`output->err` , err); }); </script >
then()返回一个新的 Promise 实例,所以它可以链式调用 如果返回新的 Promise,那么下一级.then()会在新的 Promise 状态改变之后执行
8.4 Promise 中断 Promise 的特性就是:不能中断 。 一旦执行无法知道它具体执行到哪里了,只知道在 pending,最后 resolve 或者 reject 才知道执行完毕。 Promise 可以通过在流程中使用 throw 来中断流程触发 catch 操作,也可以在某一个节点进行 reject 来中断操作它的链式调用的 then 函数。所以在链式调用的过程中是完全可以实现中断操作的。 同步的中断 Promise 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <script > const someAsyncFn = ( ) => { return new Promise (function (resolve, reject ) { setTimeout (() => { throw new Error ("强行中断,抛出异常" ); const random = Math .random (); if (random > 0.5 ) { resolve ("someAsyncFn异步操作成功" ); } else { reject ("someAsyncFn异步操作失败" ); } }, 2000 ); reject (new Error ("The promise was interrupted" )); }); }; someAsyncFn () .then ((res ) => { console .log (res); }) .catch ((err ) => { console .log (err); }); </script >
Promise 封装异步请求,中断操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <script > const someAsyncFn = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve (`someAsyncFn异步操作成功` ); } else { reject (`someAsyncFn异步操作失败` ); } }, 4000 ); }); }; const timeoutWrapper = (p, timeout = 2000 ) => { const wait = new Promise ((resolve, reject ) => { setTimeout (() => { reject (`请求超时` ); }, timeout); }); return Promise .race ([p, wait]); }; timeoutWrapper (someAsyncFn ()) .then ((res ) => { console .log (res); }) .catch ((err ) => { console .log (err); }); </script >
9. Promise 常用静态方法 9.1 Promise.all() 批量执行 Promise.all([p1, p2, p3])用于将多个 Promise 实例,包装成一个新的 Promise 实例,返回的实例就是普通的 Promise ,它接收一个数组作为参数数组里可以是 Promise 对象,也可以是别的值,只有 Promise 会等待状态改变当所有的子 Promise 都完成,该 Promise 完成 ,返回值是全部值的数组 有任何一个失败,该 Promise 失败,返回值是第一个失败的子 Promise 结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <script > const myPromise1 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise1成功" ); } else { reject ("myPromise1失败" ); } }, 2000 ); }); }; const myPromise2 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise2成功" ); } else { reject ("myPromise2失败" ); } }, 2000 ); }); }; const myPromise3 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise3成功" ); } else { reject ("myPromise3失败" ); } }, 2000 ); }); }; Promise .all ([myPromise1 (), myPromise2 (), myPromise3 ()]) .then ((res ) => { console .log ("all" , res); }) .catch ((err ) => { console .log ("all" , err); }); </script >
9.2 Promise.allSettled() 批量执行 用来确定一组异步操作是否都结束了(不管成功或失败),包含了”fulfilled“和”rejected“两种情况。 当有多个彼此不依赖的异步任务成功完成时,或者总是想知道每个子 Promise 的结果时,通常使用它。 相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个 reject 时立即结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <script > const myPromise1 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise1成功" ); } else { reject ("myPromise1失败" ); } }, 2000 ); }); }; const myPromise2 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise2成功" ); } else { reject ("myPromise2失败" ); } }, 2000 ); }); }; const myPromise3 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise3成功" ); } else { reject ("myPromise3失败" ); } }, 2000 ); }); }; Promise .allSettled ([myPromise1 (), myPromise2 (), myPromise3 ()]) .then ((res ) => { console .log ("all" , res); }) .catch ((err ) => { console .log ("all" , err); }); </script >
对于每个结果对象,都有一个 status 字符串。如果它的值为 fulfilled,则结果对象上存在一个 value。如果值为 rejected,则存在一个 reason 。value(或 reason )反映了每个 promise 决议(或拒绝)的值。
可以发现和 all 相比,allSettled 在其中一个 Promise 返回错误时还可以继续等待结果。并且不管内部的计时器定时多少毫秒,它都会等所有结果返回后按照传参传入的顺序返回 Promise 结果
应用场景: 比如用户在页面上面同时填了 3 干个独立的表单,这三个表单分三个接口提交到后端,三个接口独立,没有顺序依赖,这个时候需要等到请求全部完成后给与用户提示表单提交的情况.
面试题: 共有四个接口,第一个接口是崩溃的,但是需要返回所有接口的结果。 这一题如果使用 all,那么会直接抛出错误,所以必须使用 allSettled 方法请求数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script src ="https://unpkg.com/axios/dist/axios.min.js" > </script > <script > let a = axios.get ("http://xxxa" ); let b = axios.get ("http://xxxb" ); let c = axios.get ("http://xxxc" ); let d = axios.get ("http://xxxd" ); Promise .allSettled ([a, b, c, d]) .then ((res ) => { console .log (res); }) .catch ((err ) => { console .log (err); }); </script >
9.3 Promise.race() 批量执行 Promise.race()方法同样是将多个子 Promise 实例包装成一个新的 Promise 实例,但是只要有一个子 Promise 实例状态发生变化,就将新的 Promise 实例的状态改变,且终值由第一个完成的 Promise 提供。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <script > const myPromise1 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise1成功" ); } else { reject ("myPromise1失败" ); } }, 2000 ); }); }; const myPromise2 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise2成功" ); } else { reject ("myPromise2失败" ); } }, 2000 ); }); }; const myPromise3 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise3成功" ); } else { reject ("myPromise3失败" ); } }, 2000 ); }); }; Promise .race ([myPromise1 (), myPromise2 (), myPromise3 ()]) .then ((res ) => { console .log ("all" , res); }) .catch ((err ) => { console .log ("all" , err); }); </script >
应用场景: Promise.race()方法也有许多实际使用场景。它可以用于处理需要快速获取结果的情况,例如,当向多个不同的服务器请求同一个资源时,可以使用 Promise.race()方法来获取最快返回结果的服务器的响应,并忽略其他服务器的响应结果。或者,在一个 Web 应用程序中,需要在指定的时间内获取用户的同步输入和异步请求结果,可以使用 Promise.race()方法同时监听用户输入事件和请求结果事件,一旦其中有一个事件触发,就可以立即返回响应结果,提高应用程序的响应速度和用户体验。另外,Promise.race()方法还可以用于处理超时情况,例如,在一个 HTTP 请求的响应时间超过一定时间后,可以使用 Promise.race()方法将该请求和一个延迟一定时间的 Promise 实例包装起来,一旦有一个 Promise 进入 fulfilled 状态,就可以立即返回响应结果。如果请求在规定的时间内仍未返回,则将其取消并返回一个错误信息给用户,以提高应用程序的可用性和稳定性。
9.4 Promise.any() 批量执行 Promise.any()方法会对多个 Promise 进行竞争,直到有一个子 Promise 进入 Fulfilled 状态,Promise 实例返回该 Promise 的结果。如果所有 Promise 都进入 Rejected 状态,则返回失败状态 ,其中维护 Promise 及其状态的任何提示返回数组都是必需的。Promise.any()跟 Promise.race()方法很像,只有一点不同,就是 Promise.any()不会因为某个 Promise 变成 rejected 状态而结束,必须等到所有参数 Promise 变成 rejected 状态才会结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <script > const myPromise1 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise1成功" ); } else { reject ("myPromise1失败" ); } }, 2000 ); }); }; const myPromise2 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise2成功" ); } else { reject ("myPromise2失败" ); } }, 2000 ); }); }; const myPromise3 = ( ) => { return new Promise ((resolve, reject ) => { setTimeout (() => { const random = Math .random (); if (random > 0.5 ) { resolve ("myPromise3成功" ); } else { reject ("myPromise3失败" ); } }, 2000 ); }); }; Promise .any ([myPromise1 (), myPromise2 (), myPromise3 ()]) .then ((res ) => { console .log ("all" , res); }) .catch ((err ) => { console .log ("all" , err); }); </script >
使用场景: Promise.any()方法可以用于处理多种资源竞争的情况,例如,在一个抢单系统中,多个用户需要争夺同一个订单,系统将同时向多个用户发送请求,并使用 Promise.any()方法监听所有请求的状态,一旦有一个用户成功抢到订单,系统就立即返回订单信息并发送通知给该用户,从而提高了用户的参与度和系统的可用性。除此之外,Promise.any()方法还可以用于指定默认值或备选方案,例如,在一个多语言网站中,需要从多个 API 获取多语言翻译结果,但有些 API 可能由于网络原因或其他问题无法正常工作,这时候就可以使用 Promise.any()方法来一次性向多个 API 发送请求,并设置一个默认值或备选方案,一旦有一个 API 正常返回翻译结果,就立即返回结果给用户,如果所有 API 都无法正常工作,则返回默认值或备选方案。