• 面试题六:Promise的使用,一文详细讲解


    含义

    Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理更强大。

    所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。

     这么一看就明白了,Promise是一个构造函数,自己身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等同样很眼熟的方法。这么说用Promise new出来的对象肯定就有then、catch方法喽。

    基本用法

    ES6规定,Promise对象是一个构造函数,用来生成Promise实例

    1. var promise = new Promise(function(resolve,reject){
    2. if(/* 异步操作成功 */){
    3. resolve(value);
    4. }else{
    5. reject(error);
    6. }
    7. });

    Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。它们是两个函数,又JavaScript引擎提供,不是自己部署。

    resolve函数的作用,将Promise对象的状态从“未完成”变成“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
    reject函数的作用是,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

    看下面这段代码:
     

    1. var p = new Promise(function(resolve, reject){
    2. //做一些异步操作
    3. setTimeout(function(){
    4. console.log('执行完成');
    5. resolve('数据');
    6. }, 2000);
    7. });

    在上面的代码中,我们执行了一个异步操作,也就是setTimeout,2秒后,输出“执行完成”,并且调用resolve方法。

    运行代码,会在2秒后输出“执行完成”。注意!我只是new了一个对象,并没有调用它,我们传进去的函数就已经执行了,这是需要注意的一个细节。所以我们用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数,如:
     

    1. function runAsync(){
    2. var p = new Promise(function(resolve, reject){
    3. //做一些异步操作
    4. setTimeout(function(){
    5. console.log('执行完成');
    6. resolve('数据');
    7. }, 2000);
    8. });
    9. return p;
    10. }
    11. runAsync()

    在我们包装好的函数最后,会return出Promise对象,也就是说,执行这个函数我们得到了一个Promise对象。还记得Promise对象上有then、catch方法吧?这就是强大之处了,看下面的代码:

    1. runAsync().then(function(data){
    2. console.log(data);
    3. //后面可以用传过来的数据做些其他操作
    4. //......
    5. });

    在runAsync()的返回上直接调用then方法,then接收一个参数,是函数,并且会拿到我们在runAsync中调用resolve时传的的参数。运行这段代码,会在2秒后输出“执行完成”,紧接着输出“数据”。

    这时候你应该有所领悟了,原来then里面的函数就跟我们平时的回调函数一个意思,能够在runAsync这个异步任务执行完成之后被执行。这就是Promise的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。

    或许你会认为:我们把回调函数封装一下,给runAsync传进去不也一样吗,就像这样:
     

    1. function runAsync(callback){
    2. setTimeout(function(){
    3. console.log('执行完成');
    4. callback('数据');
    5. }, 2000);
    6. }
    7. runAsync(function(data){
    8. console.log(data);
    9. });

    那么问题来了,有多层回调该怎么办?如果callback也是一个异步操作,而且执行完后也需要有相应的回调函数,该怎么办呢?总不能再定义一个callback2,然后给callback传进去吧。而Promise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。


    链式操作

    1. function runAsync1(){
    2. var p = new Promise(function(resolve, reject){
    3. //做一些异步操作
    4. setTimeout(function(){
    5. console.log('异步任务执行完成1');
    6. resolve('数据1');
    7. }, 1000);
    8. });
    9. return p;
    10. }
    11. function runAsync2(){
    12. var p = new Promise(function(resolve, reject){
    13. //做一些异步操作
    14. setTimeout(function(){
    15. console.log('异步任务执行完成2');
    16. resolve('数据2');
    17. }, 2000);
    18. });
    19. return p;
    20. }
    21. function runAsync3(){
    22. var p = new Promise(function(resolve, reject){
    23. //做一些异步操作
    24. setTimeout(function(){
    25. console.log('异步任务执行完成3');
    26. resolve('数据3');
    27. }, 2000);
    28. });
    29. return p;
    30. }
    31. runAsync1()
    32. .then(function(data){
    33. console.log(data);
    34. return runAsync2();
    35. })
    36. .then(function(data){
    37. console.log(data);
    38. return runAsync3();
    39. })
    40. .then(function(data){
    41. console.log(data);
    42. });

    控制台输出:

    1. 异步任务执行完成1
    2. 数据1
    3. 异步任务执行完成2
    4. 数据2
    5. 异步任务执行完成3
    6. 数据3

    在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了,比如我们把上面的代码修改成这样:

    1. runAsync1()
    2. .then(function(data){
    3. console.log(data);
    4. return runAsync2();
    5. })
    6. .then(function(data){
    7. console.log(data);
    8. return '直接返回数据'; //这里直接返回数据
    9. })
    10. .then(function(data){
    11. console.log(data);
    12. });

    那么输出就变成了这样: 

    1. 异步任务执行完成1
    2. 数据1
    3. 异步任务执行完成2
    4. 数据2
    5. 直接返回数据

    reject的用法

    到这里,你应该对“Promise是什么玩意”有了最基本的了解。那么我们接着来看看ES6的Promise还有哪些功能。我们光用了resolve,还没用reject呢,它是做什么的呢?事实上,我们前面的例子都是只有“执行成功”的回调,还没有“失败”的情况,reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。看下面的代码。
     

    1. function getNumber(){
    2. var p = new Promise(function(resolve, reject){
    3. //做一些异步操作
    4. setTimeout(function(){
    5. var num = Math.ceil(Math.random()*10); //生成1-10的随机数
    6. if(num<=5){
    7. resolve(num);
    8. }
    9. else{
    10. reject('数字太大了');
    11. }
    12. }, 2000);
    13. });
    14. return p;
    15. }
    16. getNumber()
    17. .then(
    18. function(data){
    19. console.log('resolved');
    20. console.log(data);
    21. },
    22. function(reason, data){
    23. console.log('rejected');
    24. console.log(reason);
    25. }
    26. );

    getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。

    运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果: 

     catch的用法

    我们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调,用法是这样:

    1. getNumber()
    2. .then(function(data){
    3. console.log('resolved');
    4. console.log(data);
    5. })
    6. .catch(function(reason){
    7. console.log('rejected');
    8. console.log(reason);
    9. });

    效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:

    1. getNumber()
    2. .then(function(data){
    3. console.log('resolved');
    4. console.log(data);
    5. console.log(somedata); //此处的somedata未定义
    6. })
    7. .catch(function(reason){
    8. console.log('rejected');
    9. console.log(reason);
    10. });

    在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果: 

    1. resolved
    2. 4
    3. rejected
    4. ReferenceError:somedata is not defined(...)

    也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

    Ajax中的使用案例

     假如有a,b请求,b依赖a的请求数据。如下:

    1. function a(){
    2. return new Promise(function(res,rej){
    3. $.ajax({
    4. url:"a接口",
    5. type: "GET",
    6. async:true,
    7. dataType:"json",
    8. success:function(data){
    9. console.log(data,"a");
    10. res(data);
    11. }
    12. })
    13. });
    14. }
    15. function b(data){
    16. console.log(data,"data");
    17. return new Promise(function(res,rej){
    18. $.ajax({
    19. url:"b接口",
    20. type: "POST",
    21. async:true,
    22. data:JSON.stringify(data),
    23. dataType:"json",
    24. success:function(data){
    25. console.log(data,"b");
    26. res();
    27. }
    28. })
    29. });
    30. }
    31. $("#btn").click(function(){
    32. a().then(function (data){
    33. b(data);
    34. }).then(function(){
    35. })
    36. })

    注:Axios 是一个基于 promise 的 HTTP 库。

     Promise.all()的使用

    描述

    1.await 可以获得多个promise 的返回结果

    2. Promise.all 返回的也是promise,所以可以直接await Promise.all();

     1. 使用Promise.all()

    1. function fn(){
    2. return new Promise((resolve,reject)=>{
    3. let randomNum = parseInt(Math.random()*6+1);
    4. console.log(randomNum);
    5. if(randomNum>3){
    6. resolve('买');
    7. }
    8. else{
    9. reject('不买');
    10. }
    11. })
    12. }
    13. Promise.all([fn(),fn()]).then((x)=>{console.log(x,'success')},(y)=>{console.log(y,'error');});

    Promise.all 里面参数为一个数组,数组的每一项是一个返回promise 的函数调用。
    then 的第一个参数是所有promise都成功的调用,返回结果是一个数组,数组的每一项为函数promise 的返回结果。
    then 的第二个参数:返回结果有一个失败则执行失败的回调,拿到的是第一个失败的值。

    2. 使用await

    await 是可以获得多个promise 返回结果的,Promise.all()返回的也是promise结果。所以想要使用await 拿到多个promise的返回值,可以直接await Promise.all();

    1. function fn(){
    2. return new Promise((resolve,reject)=>{
    3. let randomNum = parseInt(Math.random()*6+1);
    4. console.log(randomNum);
    5. if(randomNum>3){
    6. resolve('买');
    7. }
    8. else{
    9. reject('不买');
    10. }
    11. })
    12. }
    13. async function test(){
    14. try{
    15. let res = await Promise.all([fn(),fn()]);
    16. console.log(res,'success');
    17. }
    18. catch(error){
    19. console.log(error,'error');
    20. }
    21. }
    22. test();
    • Promise.all([fn(),fn()]) 都返回resolve(); 才能够拿到成功的返回值
    • Promise.all([fn(),fn()]) 有一个返回reject(), 则进入catch(error), 拿到失败的返回值

    实际案例

    这边引入两个接口,获取结果,然后取到两个接口的值的和。通过Promise.all,能够获得和。

    1. selectRewiewTaskNum() {
    2. console.log('我要获取复核的数据')
    3. let promiseValues = [ApiSign.selectReviewCount(), ApiSign.selectAssetPoolNum()]
    4. Promise.all(promiseValues).then(res => {
    5. console.log('res', res)
    6. this.billTaskNum = res[0].data.billTaskNum
    7. this.assetPoolTaskNum = res[1].data.assetPoolTaskNum
    8. this.taskNumber2 = this.assetPoolTaskNum + this.billTaskNum
    9. console.log('数据是', this.assetPoolTaskNum, this.billTaskNum)
    10. })
    11. },

    Promise.race()使用 

    Promise.race其实使用的并不多,如果真要使用。我们可以提出这样一个需求:

    比如:点击按钮发请求,当后端的接口超过一定时间,假设超过三秒,没有返回结果,我们就提示用户请求超时

    1. <script>
    2. export default {
    3. name: "App",
    4. methods: {
    5. async clickFn() {
    6. // 第一个异步任务
    7. function asyncOne() {
    8. let async1 = new Promise(async (resolve, reject) => {
    9. setTimeout(() => {
    10. // 这里我们用定时器模拟后端发请求的返回的结果,毕竟都是异步的
    11. let apiData1 = "某个请求";
    12. resolve(apiData1);
    13. }, 4000);
    14. });
    15. return async1;
    16. }
    17. console.log("异步任务一", asyncOne()); // 返回的是pending状态的Promise对象
    18. // 第二个异步任务
    19. function asyncTwo() {
    20. let async2 = new Promise(async (resolve, reject) => {
    21. setTimeout(() => {
    22. let apiData2 = "超时提示";
    23. resolve(apiData2);
    24. }, 3000);
    25. });
    26. return async2;
    27. }
    28. console.log("异步任务二", asyncTwo()); // 返回的是pending状态的Promise对象
    29. // Promise.race接收的参数也是数组,和Promise.all类似。只不过race方法得到的结果只有一个
    30. // 就是谁跑的快,结果就使用谁的值
    31. let paramsArr = [asyncOne(), asyncTwo()]
    32. Promise
    33. .race(paramsArr)
    34. .then((value) => {
    35. console.log("Promise.race方法的结果", value);
    36. if (value == "超时提示") {
    37. this.$message({
    38. type:"warning",
    39. message:"接口请求超时了"
    40. })
    41. }else{
    42. console.log('正常操作即可');
    43. }
    44. })
    45. },
    46. },
    47. };
    48. script>

    总结:其实刚开始学的时候对于这个很晦涩,但是你想清楚业务逻辑你就知道怎么做了,比如说目前我们有三种情况,看看我们如何使用promise

    第一种:后端给了A接口,B接口,C接口,通过三个接口可以获取三个业务的数量,那么你需要在工作台展示三个业务的数量和总数,那么你就需要去用Promise.all([A,B,C]).then(()=>{})

    第二种:有个小游戏,页面有三个接口,三个同时调用,第一个点击的接口就是我们需要的结果,Promise.race().then()就可以了

    第三种:我们后端给了两个接口A和B,需要我们调用完A获取结果后再传参B获取接口,那么如果我们用这样的太难看了。

    1. function fn (){
    2. JIEKOU1().then((res) => {
    3. if (res.code == 200) {
    4. jiekou2(res.data).then((res) => {
    5. if(res.code == 200) {
    6. }
    7. })
    8. }
    9. })
    10. }

    可以这样

    1. function a(handleView) {
    2. return new Promise(resolve => {
    3. jiekou1().then(res => {
    4. if (res.success) {
    5. if (handleView) {
    6. this.menuTreeData = res.data
    7. resolve()
    8. return
    9. }
    10. this.menuTreeData = res.data
    11. resolve()
    12. }
    13. })
    14. })
    15. }
    16. a('handleView').then(() => {
    17. jiekou2().then(res => {
    18. if (res.success) {
    19. }
    20. })
    21. })

    其实你可以理解为promise想把两个异步或者多个异步能够代码分开吗,如果你不用,那么就会出现所有的代码都在一块,这样就可以完美解决这个问题了,不过温馨提示下,promise里边的代码一定要认真写,因为promise是可以吃掉你的错误继续运行下边的代码的。

  • 相关阅读:
    华为云云耀云服务器L实例评测|部署个人音乐流媒体服务器 navidrome
    小心golang中的无类型常量
    1078. Bigram 分词
    将cpu版本的pytorch换成gpu版本
    1Panel应用推荐:Uptime Kuma
    Goldengate
    Intel® Hyper-Threading Technolog 超线程技术
    揭秘Spring Boot内嵌Tomcat原理
    Springboot接入ChatGPT 续
    [Linux] PXE批量装机
  • 原文地址:https://blog.csdn.net/2201_75705263/article/details/133349292