Promise已经出现很久了,但鉴于很多初学者还不是很了解也经常有人问到这个,因此就想写一篇文章来讲解一下这方面的知识,本文会尽量用一些简单的实例来说明一些问题,也会尽量做到通俗易懂、简单明了。
参考文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
一、什么是Promise
Promise是ES6原生提供的一个全局的构造函数,它提供了很多方法供我们使用,它解决了异步操作流程中回掉函数层层嵌套的问题。
当然,看完上面的解释,相信不会有几个人能真正看懂(大神除外)。少一份套路,多一份真诚,牛逼还是要吹的,但要把握一个度,接下来我们用一个简单的实例来看下:
//先实例化一个Promise
var pro=new Promise(function(resolve,reject){
var name="李";
resolve(name);//将name传递下去
});
pro.then(function(data){
//输出成功传来的数据
console.log(data);
//先处理数据
var newData1=data+"可";
//将处理后的数据传递下去
return Promise.resolve(newData1);
}).then(function(data){
//输出成功传来的数据
console.log(data);
//先处理数据
var newData2=data+"可";
//将处理后的数据传递下去
return Promise.resolve(newData2);
}).then(function(data){
//输出最终的数据
console.log(data);
//故意抛出一个异常
throw "Promise不想和你说话,并向你抛出了一个异常!!"
}).catch(function(mes){
//这里处理被拒绝或发生异常的情况
//mes参数代表被拒接或异常时携带的信息
console.log(mes);
});
/* 输出结果如下 */
//李
//李可
//李可可
//Promise不想和你说话,并向你抛出了一个异常!!
看着是不是很牛逼也很神奇啊
,哈哈,我猜肯定没有,这写的啥啊,屁用都没有是吧?肯定是的。但是先别急,上面的代码只是让大家先对Promise有个初步的了解,实际应用的时候肯定不会这么简单啊。另外,这里如果还是没搞懂Promise到底是个什么东西也不要着急,毕竟搞对象一直都不是什么容易的事儿,而何况是搞一个这么牛逼的对象是吧。
下面我们就来详细地逐个讲解每个API
二、then和catch方法
then和catch是Promise.prototype上的两个最常用的方法,下面我们将 从一个最简单的实例开始:
var p1 = new Promise(function(resolve, reject) { resolve("恭喜你,你赢了");//将来传递给下一层的数据就是里面的参数 }); p1.then(function(data) { var num=Math.random();//生成一个随机数 if(num>0.1){ //用户有90%的可能性会赢 console.log(data); return; } throw "厉害了word哥,这都能输!"; }).catch(function(e) { console.log(e);//如果输了,则会被嘲讽 });1.在这里,我们首先实例化了一个Promise,并将一个函数作为参数传入进去,函数中传入了两个参数resolve和reject,需要注意的是这两个参数都是函数,使用方法是resolve(data)和reject(data),参数data可以是任意数据类型,参数data代表了将要传递下去的数据。
2.then用来接收上一层传来的数据并处理数据,必要时可以当前层继续向下传递数据(怎样再继续传递数据接下来会讲)。
3.catch用来捕获异常或捕获拒绝信息,在这里的参数e就代表了抛出的异常信息。
4.需要注意的是只有Promise对象才有向下传递数据的功能,才有then和catch等方法,所以使用这些牛逼的特性之前必须先new Promise().
三、Promise.resolve()和Promise.reject()
resolve和reject是Promise的两个比较常用的静态方法,关于什么是js的静态方法、原型方法和公共方法大家可以去自行百度。
前面我们提到了如何实现数据的多级传递,其实我们一开始举的例子里面就早已经将其实现了,但是看起来貌似有点生涩难懂,有些同学可能已经想到了,根据上面第4点所说的,我们直接在then里面再次new一个Promise并返回不就行了吗,那我们现在就来测试一下,看看是否可行:
new Promise(function(resolve,reject){ resolve(1); }).then(function(data){ //返回一个Promise的实例 return new Promise(function(resolve,reject){ resolve(data+1); }) }).then(function(data){ //返回一个Promise的实例 return new Promise(function(resolve,reject){ resolve(data+1); }) }).then(function(data){ //返回一个Promise的实例 return new Promise(function(resolve,reject){ resolve(data+1); }) }).then(function(data){ console.log(data); }) //4哈哈,恭喜某些同学,还真可以,但是我们刚开始就说了 " 它解决了异步操作流程中回掉函数层层嵌套的问题 ",然而我们现在却已经嵌套了三层,虽然嵌套的不是很多,但是我们追求的是完美不是吗?如果还有更复杂的需求那该怎么办,我们岂不是深陷其中而无法自拔 。
现在我们来看下Promise.resolve()和Promise.reject()是怎么用的:
Promise.reject()比较简单,它返回一个Promise的实例,并且这个被返回的实例携带着被拒绝的原因(传入的参数),一旦遇到Promise.reject整个流程就会中断,catch也会立即接收到这个原因。当然我们也可以使用throw来代替它,效果是一样的。
这里我们主要讲解一下Promise.resolve()。这里偷个懒,直接引用MDN的解释,
Promise.resolve(value)方法
返回一个以给定值解析后的Promise实例。但如果这个值是个thenable(即带有then方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态(指resolved/rejected/pending/settled);否则以该值为成功状态返回promise对象。
当然,听了这个我们肯定还是云里雾里的啥都没听懂,没关系,我们用实例说话:
Promise.resolve(1)//1 .then(function(data){ return Promise.resolve(data+1);//2 }).then(function(data){ return Promise.reject(data+1+"时被拒绝");//3 }).then(function(data){ return Promise.resolve(data+1);//4 }).then(function(data){ console.log(data);//结果 }).catch(function(mes){ console.log(mes);//输出拒绝的"原因" }); //3时被拒绝我们说了,Promise.resolve会返回一个Promise实例,因此上面的代码也就不难理解了,每次都return一个Promise.resolve,而Promise.resolve又会返回一个Promise实例,因此我们可以接着再后面跟"then",从而实现了多级链式调用。
另外我们也说了,Promise.reject会返回一个Promise的实例,并跳转到catch那一层,catch也会得到被拒绝的原因,因此在3的时候立即中断并被catch捕获。Promise.reject(mes)其实可以用throw mes来代替的,例如这一句代码可以使用throw data+1+"时被拒绝";来代替,
Promise.resolve()的特殊用法1:除了可以传数值、字符串、boolean、数组和普通对象之外还可以传入另一个Promise对象实例,例如下面的用法:
/*****************1******************/ var original = Promise.resolve(true); var cast = Promise.resolve(original); cast.then(function(v) { console.log(v); // true }); /*****************2******************/ var original = new Promise(function(resolve,reject){ resolve(true); }); var cast = Promise.resolve(original); cast.then(function(v) { console.log(v); // true });Promise.resolve()的特殊用法2:传入一个特殊的对象(含有then方法),例如下面的代码
/***********************************1************************************/ // Resolve一个thennable对象 var p1 = Promise.resolve({ then: function(resolve, reject) { resolve("fulfilled!"); } }); console.log(p1 instanceof Promise) // true, 这是一个Promise对象 p1.then(function(v) { console.log(v); // 输出"fulfilled!" }, function(e) { // 不会被调用 }); /**********************************2************************************/ // Thenable在callback之前抛出异常 // Promise rejects var thenable = { then: function(resolve) { throw new TypeError("Throwing"); resolve("Resolving"); }}; var p2 = Promise.resolve(thenable); p2.then(function(v) { // 不会被调用 }, function(e) { console.log(e); // TypeError: Throwing }); /************************************3*************************************/ // Thenable在callback之后抛出异常 // Promise resolves var thenable = { then: function(resolve) { resolve("Resolving"); throw new TypeError("Throwing"); }}; var p3 = Promise.resolve(thenable); p3.then(function(v) { console.log(v); // 输出"Resolving" }, function(e) { // 不会被调用 });细心的小伙伴可能就会发现其实传入thenable(含有then方法的对象)和传入一个Promise的实例,效果基本上是一样的,其用法和传入一个new Promise(function(resolve,reject){})也非常类似,刚开始我们只需要习惯一种用法即可。
四,Promise的其它写法
/******************1******************/ new Promise(function(res,rej){ rej("err1"); }).then(function(){ },function(err){ //捕获到错误 console.log(err); }) /*******************2*****************/ Promise.reject("err1").then(function(){ },function(err){ //捕获到错误 console.log(err); }) /*******************3*****************/ Promise.resolve(1) .then(function(data){ throw new TypeError("err1") }).then(function(){ },function(err){ //捕获到错误 console.log(err); });总之,then可以有第二个参数,这个参数是一个函数,用来捕获上一层可能会传来的错误。
五、Promise的其它静态方法
Promise除了上面提到的resolve和reject方法之外,还有两个静态方法
-
Promise.all()
请原谅我比较懒,再次复制MDN的解释,Promise.all(iterable)
方法返回一个promise,该promise会等iterable参数内的所有promise都被resolve后被resolve,或以第一个promise被reject的原因而reject 。
接下来还是先看几个实例吧:/*****************1*********************/ var promise = Promise.resolve(3); Promise.all([true, promise]) .then(values => { console.log(values); // [true, 3] }); /*****************2*********************/ var arr=[1,Promise.reject("err"),Promise.resolve(3)]; Promise.all(arr) .then(values =>{ console.log(values);//不会执行 }).catch(err => { console.log(err);//err }); /*****************3*********************/ var arr=[1,Promise.reject("err"),Promise.resolve(3)]; Promise.all(arr) .then(values =>{ console.log(values);//不会执行 },err => { console.log(err);//err });
总结一下,Promise.all的参数是一个数组,这个数组里面的元素可以是Promise的实例,也可以不是,如果不是(比如传入基本数据类型),则会被自动转换为一个Promise对象,最终会在then的第一个参数里得到一个数组,这个数组里面的每个值对应着当时传入的数组里每个元素(是一个Promise的实例)resolve的值。如果传入数组里面的某一个元素被reject了,那么后面将会立即捕获到这个reject的原因,并且不再理会其它传入的promise是否被resolve。 -
Promise.race()
Promise.race(iterable)方法返回一个promise,这个promise在iterable中的
任意一个promise被解决或拒绝后,
立刻以相同的解决值
被解决或以相同的拒绝原因被
拒绝。其中iterable是一个存放着若干个Promise的数组。
iterable内的每一个Promise的实例就像在同一起跑线上的运动员,他们即将进行一场百米赛跑,谁先到达终点谁就会被接纳,而其他运动员则竟被忽略掉。
通俗来讲,
看代码:/***************************1******************************/ var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, "一"); }); var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "二"); }); Promise.race([p1, p2]).then(function(value) { console.log(value); // "二" // 两个都解决,但p2更快 }); /***************************2******************************/ var p3 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "三"); }); var p4 = new Promise(function(resolve, reject) { setTimeout(reject, 500, "四"); }); Promise.race([p3, p4]).then(function(value) { console.log(value); // "三" // p3更快,所以被解决(resolve)了 }, function(reason) { // 未被执行 }); /***************************3******************************/ var p5 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, "五"); }); var p6 = new Promise(function(resolve, reject) { setTimeout(reject, 100, "六"); }); Promise.race([p5, p6]).then(function(value) { // 未被执行 }, function(reason) { console.log(reason); // "六" // p6更快,所以被拒绝(reject了) });
六、实际应用
讲了这么多废话,终于到了实际应用了,对于Promise来说,用到ajax当中是最合适不过了,我们可以使用原生的ajax结合Promise来写一个插件,要求可以链式调用
'use strict'; // A-> $http function is implemented in order to follow the standard Adapter pattern function $http(url){ // A small example of object var core = { // Method that performs the ajax request ajax : function (method, url, args) { // Creating a promise var promise = new Promise( function (resolve, reject) { // Instantiates the XMLHttpRequest var client = new XMLHttpRequest(); var uri = url; if (args && (method === 'POST' || method === 'PUT')) { uri += '?'; var argcount = 0; for (var key in args) { if (args.hasOwnProperty(key)) { if (argcount++) { uri += '&'; } uri += encodeURIComponent(key) + '=' + encodeURIComponent(args[key]); } } } client.open(method, uri); client.send(); client.onload = function () { if (this.status >= 200 && this.status < 300) { // Performs the function "resolve" when this.status is equal to 2xx resolve(this.response); } else { // Performs the function "reject" when this.status is different than 2xx reject(this.statusText); } }; client.onerror = function () { reject(this.statusText); }; }); // Return the promise return promise; } }; // Adapter pattern return { 'get' : function(args) { return core.ajax('GET', url, args); }, 'post' : function(args) { return core.ajax('POST', url, args); }, 'put' : function(args) { return core.ajax('PUT', url, args); }, 'delete' : function(args) { return core.ajax('DELETE', url, args); } }; }; // End A // B-> Here you define its functions and its payload var mdnAPI = 'https://developer.mozilla.org/en-US/search.json'; var payload = { 'topic' : 'js', 'q' : 'Promise' }; var callback = { success : function(data){ console.log(1, 'success', JSON.parse(data)); }, error : function(data){ console.log(2, 'error', JSON.parse(data)); } }; // End B // Executes the method call $http(mdnAPI) .get(payload) .then(callback.success) .catch(callback.error); // Executes the method call but an alternative way (1) to handle Promise Reject case $http(mdnAPI) .get(payload) .then(callback.success, callback.error); // Executes the method call but an alternative way (2) to handle Promise Reject case $http(mdnAPI) .get(payload) .then(callback.success) .then(undefined, callback.error); $http(mdnAPI) .get(payload) .then(function(data){ console.log(data); //这里可以继续return一个请求的发送 //例如return $http(mdnAPI).get(payload) //内部会return一个Promise的实例,因此下面就可以继续"then" }) .catch(callback.error);