0

Vue进阶(四十五):精解 ES6 Promise 用法

 1 year ago
source link: https://blog.51cto.com/shq5785/6167793
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Vue进阶(四十五):精解 ES6 Promise 用法

精选 原创

NoSilverBullet 2023-04-04 08:45:45 博主文章分类:Vue2 ©著作权

文章标签 数据 异步操作 回调函数 文章分类 Vue.js 前端开发 指尖人生 阅读数162

复杂难懂概念先不讲,我们先简单粗暴地把Promise用一下,有个直观感受。那么第一个问题来了,Promise是什么呢?是类?对象?数组?函数?别猜了,console.dir(Promise)直接打印出来看看。

Vue进阶(四十五):精解 ES6 Promise 用法_数据

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

var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
        console.log('执行完成');
        resolve('随便什么数据');
    }, 2000);
});

Promise的构造函数接收一个函数参数,并且传入两个参数:resolvereject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。

其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,resolve是将Promise的状态置为fullfiledreject是将Promise的状态置为rejected。不过开始阶段可以先这么理解,后面再细究概念。

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

function runAsync(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('执行完成');
            resolve('随便什么数据');
        }, 2000);
    });
    return p;            
}
runAsync()

这时候你可能会有两个疑问:

  1. 包装这么一个函数有什么作用?
  2. resolve(‘随便什么数据’);这是作什么的?

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

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

runAsync()的返回上直接调用then方法,then接收一个参数,是函数,并且会拿到我们在runAsync中调用resolve时传的的参数。

运行这段代码,会在2秒后输出“执行完成”,紧接着输出“随便什么数据”。 这时候你应该有所领悟了,原来then里面的函数就跟我们平时的回调函数一个意思,能够在runAsync这个异步任务执行完成之后被执行。

这就是Promise的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。 你可能会不屑一顾,Promise就这点能耐?我把回调函数封装一下,给runAsync传进去不也一样吗,就像这样:

function runAsync(callback){
    setTimeout(function(){
        console.log('执行完成');
        callback('随便什么数据');
    }, 2000);
}
 
runAsync(function(data){
    console.log(data);
});

效果也是一样的,还费劲用Promise干嘛?

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

二、链式操作用法

所以,从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护、传递状态的方式来使得回调函数能够得到及时调用,它比传递callback函数简单、灵活。所以使用Promise的正确场景是这样的:

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

这样能够按顺序,每隔两秒输出每个异步回调中的内容,在runAsync2中传给resolve的数据,能在接下来的then方法中拿到。运行结果如下:
Vue进阶(四十五):精解 ES6 Promise 用法_异步操作_02
猜猜runAsync1runAsync2runAsync3这三个函数都是如何定义的?没错,就是下面这样:

function runAsync1(){
   var p = new Promise(function(resolve, reject){
      //做一些异步操作
      setTimeout(function(){
          console.log('异步任务1执行完成');
          resolve('随便什么数据1');
      }, 1000);
   });
   return p;            
 }
 function runAsync2(){
   var p = new Promise(function(resolve, reject){
      //做一些异步操作
      setTimeout(function(){
          console.log('异步任务2执行完成');
          resolve('随便什么数据2');
      }, 2000);
   });
   return p;            
 }
 function runAsync3(){
   var p = new Promise(function(resolve, reject){
      //做一些异步操作
      setTimeout(function(){
          console.log('异步任务3执行完成');
          resolve('随便什么数据3');
      }, 2000);
   });
   return p;            
 }

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

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return '直接返回数据';  //这里直接返回数据
})
.then(function(data){
    console.log(data);
});

那么输出就变成了这样:
Vue进阶(四十五):精解 ES6 Promise 用法_异步操作_03

三、reject 用法

到这里,你应该对Promise有了最基本了解。那么我们接着来看看ES6Promise还有哪些功能。上面只是使用了resolve,还没用reject,它是做什么的呢?

事实上,我们前面的例子都是只有“执行成功”的回调,还没有“失败”的情况,reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。看下面的代码:

function getNumber(){
  var p = new Promise(function(resolve, reject){
      //做一些异步操作
      setTimeout(function(){
          var num = Math.ceil(Math.random()*10); //生成1-10的随机数
          if(num<=5){
              resolve(num);
          }
          else{
              reject('数字太大了');
          }
      }, 2000);
  });
  return p;            
}
 
getNumber()
.then(
   function(data){
       console.log('resolved');
       console.log(data);
   }, 
   function(reason, data){
       console.log('rejected');
       console.log(reason);
   }
);

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

运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果:
Vue进阶(四十五):精解 ES6 Promise 用法_回调函数_04
Vue进阶(四十五):精解 ES6 Promise 用法_回调函数_05

四、catch 用法

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

getNumber()
	.then(function(data){
	    console.log('resolved');
	    console.log(data);
	})
	.catch(function(reason){
	    console.log('rejected');
	    console.log(reason);
	});

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

getNumber()
	.then(function(data){
	    console.log('resolved');
	    console.log(data);
	    console.log(somedata); //此处的somedata未定义
	})
	.catch(function(reason){
	    console.log('rejected');
	    console.log(reason);
	});

resolve回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果:
Vue进阶(四十五):精解 ES6 Promise 用法_异步操作_06
也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

五、all 用法

Promiseall方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的runAsync1、runAsync2、runAsync3这三个函数,看下面的例子:

Promise
	.all([runAsync1(), runAsync2(), runAsync3()])
	.then(function(results){
	    console.log(results);
	});

Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作并行执行,等到它们都执行完后才会进到then里面。

那么,三个异步操作返回的数据哪里去了呢?都在then里面,all会把所有异步操作结果放进一个数组中传给then,就是上面的results。所以上面代码的输出结果就是:

Vue进阶(四十五):精解 ES6 Promise 用法_回调函数_07

有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,对于游戏类素材比较多的应用,打开网页时,预加载需要用到的各种资源如:图片、flash以及各种静态文件。待所有资源都加载完后,我们再进行页面初始化。

六、race 用法

all方法的执行效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就有赛跑的含义。race用法与all一样,把上面runAsync1的延时改为1秒来看一下:

Promise
	.race([runAsync1(), runAsync2(), runAsync3()])
	.then(function(results){
	    console.log(results);
	});

这三个异步操作同样是并行执行的。结果你应该可以猜到,1秒后runAsync1已经执行完了,此时then里面的就执行了。结果如下:
Vue进阶(四十五):精解 ES6 Promise 用法_异步操作_08
你猜对了吗?不完全,是吧。在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。 这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应操作,代码如下:

//请求某个图片资源
function requestImg(){
   var p = new Promise(function(resolve, reject){
       var img = new Image();
       img.onload = function(){
           resolve(img);
       }
       img.src = 'xxxxxx';
   });
   return p;
}
 
//延时函数,用于给请求计时
function timeout(){
   var p = new Promise(function(resolve, reject){
       setTimeout(function(){
           reject('图片请求超时');
       }, 5000);
   });
   return p;
}
 
Promise
	.race([requestImg(), timeout()])
	.then(function(results){
	    console.log(results);
	})
	.catch(function(reason){
	    console.log(reason);
	});

requestImg函数会异步请求一张图片,把地址写为"xxxxxx",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果如下:
Vue进阶(四十五):精解 ES6 Promise 用法_异步操作_09

ES6 Promise的内容就以上这些吗?是的,能用到的基本就这些。 我怎么还见过donefinallysuccessfail等,这些是啥?这些并不在Promise标准中,而是我们自己实现的语法糖。本文中所有异步操作均以setTimeout为例子,之所以不使用ajax是为了避免引起混淆,因为谈起ajax,很多人的第一反应就是jquery的ajax,而jquery又有自己的Promise实现。如果你理解了原理,就知道使用setTimeout和使用ajax是一样的意思。


Recommend

  • 61
    • 掘金 juejin.im 6 years ago
    • Cache

    ES6 Promise的使用和理解

    JS的异步 JS语言的执行环境是“单线程”的,即指一次只能完成一件任务;如果有多个任务,那么必须排队,前面一个任务完成,再执行后一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等...

  • 32
    • segmentfault.com 5 years ago
    • Cache

    关于 ES6 中 Promise 的面试题

    说明 最近在复习 Promise 的知识,所以就做了一些题,这里挑出几道题,大家一起看看吧。 题目一 const promise = new Promise((resolve, reject) => { console.log(1); resolve(); conso...

  • 17
    • www.cnblogs.com 4 years ago
    • Cache

    ES6中的Promise使用总结

    One、什么是Promise? Promise是异步编程的解决方案,而它本身也就是一个构造函数,比传统的异步解决【回调函数】和【事件】更合理,更强大。 Two、Promise有何作用? 作用:解决回调地狱 当我们在写逻辑...

  • 28
    • www.cnblogs.com 4 years ago
    • Cache

    ES6语法——Promise对象

    一、概念 Promise是异步编程的一种解决方案(解决回调地狱的问题),是一个能够获取异步操作信息的对象。Promise的内部保存着某个未来才会结束的事件(通常是一个异步操作) 二、特点 1.Promise对象...

  • 13

    用一个例子理解 ES6 的 export/import 用法 2020-08-09 — Yanbin 使用了一段时间的 Vue.js 以后,其中有大量的 ES6 的 export/import 用法,如 import axios from 'axios'; import Ho...

  • 16

    思路探讨(四十五) 思考方式的转变事情太多、没有时间写文章其实都是借口,看漫画看抖音怎么就有时间了呢,这方面我一直在于自己斗争。另外一方面,其实对我来说有一个很大的障碍,就是羞于把自己的想法说出来,怕自己的想法不成熟会被人嘲笑,其实根本就...

  • 8

    群晖NAS非官方入门手册 篇四十五:白群至尊福利 黑群仰慕不已 DSM7.0正式版升级面面观 ...

  • 9
    • segmentfault.com 3 years ago
    • Cache

    Java 8 CompletableFuture 对比 ES6 Promise

    Java 8 CompletableFuture 对比 ES6 PromiseJavaScript 语言的执行环境是单线程的,异步编程对于 JavaScript 来说必不可少。JavaScript 传统异步解决方案主要是通过回调函数,而回调函数最...

  • 8

  • 6

    ES6 Promise详解 本文主要是对Promise本身的用法做...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK