ES6中,Promise对象可以理解为一次执行的异步操作,使用Promise对象之后可以使用一种链式调用方法来组织代码,让代码更加直观。
在使用ajax发送请求后拿到数据,往往要将数据传送到其他请求中,那么就需要编写这样的代码:
$.ajax({
url: '',
dataType: 'json',
success: function (data) {
//获取数据后将数据传送到下一个请求
var id = data.id;
$.ajax({
url: '',
data: {"id": id},
success: function(){
//...
}
});
}
});
这样的代码有以下的缺点:
1.后一个请求依赖前一个请求,等待前一个请求成功后才能将数据往下一个请求传送,会出现多个ajax嵌套的情况,代码不够直观。
2.如果前后2个请求不需要传递参数的情况下,那么后一个请求也需要在前一个请求成功后才执行下一个操作,这种情况下依旧需要编写以上代码,同样使得代码很不直观
针对这种情况,我们就可以使用promise对象来解决这样的问题。先看一下目前浏览器支持promise的情况:
要创建promise对象,可以使用new来调用promise构造器来进行实例化。如下:
var promise = new Promise(function(resolve,reject){
//异步处理;
//成功调用resolve往下传递参数,且只接受一个参数;
//失败调用reject往下传递参数,且只接受一个参数;
});
对通过new生成的promise对象,为了设置其值在resolve(成功)和reject(失败)时调用的回调函数,可以使用promise.then()来实现方法,如下:
promise.then(onFulfilled,onRejected);
成功时调用onFulfilled方法,失败时调用onRejected方法
promise.then成功和失败的时候都可以使用,如果出现异常的情况下可以采用:
promise.then(undefined,OnRejected)这种方式,只指定OnRejected回调函数即可,不过针对这种情况有更好的选择,那就是catch方法,如下:promise.catch(onRejected)
理解promise.resolve:
一般情况下我们都会使用new Promise()来创建promise对象,但是我们也可以使用promise.resole和promise.reject这2个方法:
Promise.resolve(value)的返回值是一个promise对象,我们可以用返回值.then调用,如下:
Promise.resolve(11).then(function(value){
console.log(value);//11
})
resolve(11)中,会让promise对象进入确定(resolve)状态,并将参数11传递到后面的then所指定的onFulfilled函数;
理解promise.reject:
和promise.resolve差不对,promise.reject也是创建promise的快捷途径,会让promise对象进入reject状态,比如如下代码:
new Promise(function(resolve,reject){
reject(new Error("this is an error"));
})
理解promise异步调用的操作:
var promise = new Promise(function(resolve,reject){
console.log(1);
resolve(3);
});
promise.then(function(value){
console.log(value);
});
console.log(2);
//1,2,3
首先代码从上往下执行,先输出1,然后调用resolve(3),这时候promise的状态变为确定,调用onFulfilled方法,Promise.then是成功和失败时都可以使用,因此调用第一个函数是成功调用,但是Promise对象是异步方式调用的,所以先执行console.log(2),再调用promise.then打印3。
为了解决同步或者异步混乱的问题,可以使用promise对象来进行解决:如下代码
function readyPromise () {
return new Promise(function(resolve,reject){
var readyState = document.readyStaty;
if(readyState == 'interactive' || readyState == 'complete'){
resolve();
} else {
window.addEventListener('DOMContentLoaded',resolve);
}
});
}
readyPromise().then(function(){
console.log("DOM load Success");
});
console.log("我是同步的,先执行我")
//我是同步的,先执行我
//DOM Load Success
promise有三种状态,Pending,Resolve,Reject;
Pending可以理解为Promise对象实例创建时候的初始状态,Resolve是成功时候的状态,Reject是失败时候的状态。
理解then:
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);
}).then(testPromise2)
.then(testPromise3);
function testPromise2(){
console.log(2);
}
function testPromise3(){
console.log(3);
}
输出为:hello world 2 3;
上面代码是then的链式调用方式,代码的执行是按顺序进行输出,使用链式调用的原因是每次调用后都会返回promise对象。
理解promise.catch()方法:
Promise.catch是promise.then(undefined,onRejected)方法的一个别名,用来注册当promise对象状态变为Rejected状态的回调函数。如下代码:
var promise = new Promise.reject(new Error("mes"));
promise.catch(function(error){
console.log(error);
})
打印如下:
理解每次调用then都会返回一个新创建的promise对象
不管是then还是catch方法调用,都返回一个新的promise对象;
下面我们来看看代码如下:
var promise1 = new Promise(function(resolve){
resolve(1);
});
var thenPromise = promise1.then(function(value){
console.log(value);
});
var catchPromise = thenPromise.catch(function(error){
console.log(error);
});
console.log(promise1 !== thenPromise); // true
console.log(thenPromise !== catchPromise); //true
如上代码,打印的都是true,这说明不管是then还是catch都返回了和新创建的promise是不同的对象;
如果我们知道了then方法每次都会创建返回一个新的promise对象的话,那么就不难理解下面的代码了;如下
var promise1 = new Promise(function(resolve){
resolve(1);
});
promise1.then(function(value){
return value * 2;
});
promise1.then(function(value){
return value * 2;
});
promise1.then(function(value){
console.log("1"+value);
});
如上的代码;打印出11;因为他们每次调用then方法时,是使用的不同的promise对象;因此最后打印的value还是1;但是如果我们then方法是连续调用的话,那情况就不一样了,比如如下代码:
var promise1 = new Promise(function(resolve){
resolve(2);
});
promise1.then(function(value){
return value * 2;
}).then(function(value){
return value * 2;
}).then(function(value){
console.log("1"+value);
});
打印出18,即 “1” + 2*2*2 = 18;
上面第一种方法没有使用方法链的调用,上面第一种那种写法then 调用几乎是同时开始进行的,且传给每个then的value都是1;
第二种方式是使用方法链的then,使多个then方法连接在一起了,因此函数会严格执行 resolve – then — then – then的顺序执行,并且传递每个then方法的value的值都是前一个promise对象中return的值;因此最后的结果就是18了;
现在我们再回过头一刚开始我们讨论的为什么要使用promise的原因的问题了,比如2个ajax请求,后一个ajax请求需要获取到前一个ajax请求的数据,我们之前在使用jquery写的代码很多缺点,但是改用promise后可以这样写:
var ajaxPromise = new Promise(function(resolve,reject){
resolve();
});
ajaxPromise.then(function(){
$.ajax({
url = '',
dataType = 'json',
success: function(data){
var id = data.id
return id;
}
})
}).then(function(id){
$.ajax({
url = '',
dataType = 'json',
data: {"id": id},
success: function(data){
console.log(data);
}
})
});
理解Promise.all
Promise.all可以接受一个元素为Promise对象的数组作为参数,当这个数组里面所有的promise对象都变为resolve时,该方法才会返回。如下代码:
var promise1 = new Promise(function(resolve){
setTimeout(function(){
resolve(1);
},3000);
});
var promise2 = new Promise(function(resolve){
setTimeout(function(){
resolve(2);
},1000);
});
Promise.all([promise1,promise2]).then(function(value){
console.log(value); // 打印[1,2]
});
如上代码 打印的是[1,2]; 如上我们看到promise1对象中的setTimeout是3秒的时间,而promise2对象中的setTimeout是1秒的时间,但是在Promise.all方法中会按照数组的原先顺序将结果返回;
在我们平时的需求中,或许有这种情况的需求,比如我们需要发2个ajax请求时,不管他们的先后顺序,当这2个ajax请求都同时成功后,我们需要执行某些操作的情况下,这种情况非常适合;
理解Promise.race
如上可知:Promise.all 在接收到的所有对象promise都变为FulFilled或者 Rejected状态之后才会继续后面的处理,但是Promise.race的含义是只要有一个promise对象进入FulFilled或者Rejected状态的话,程序就会停止,且会继续后面的处理逻辑;如下代码:
// `delay`毫秒后执行resolve
function timerPromise(delay){
return new Promise(function(resolve){
setTimeout(function(){
resolve(delay);
},delay);
});
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
timerPromise(1),
timerPromise(32),
timerPromise(64),
timerPromise(128)
]).then(function (value) {
console.log(value); // => 1
});
如上代码创建了4个promise对象,这些promise对象分别在1ms,32ms,64ms,128ms后变为确定状态,并且在第一个变为确定状态后1ms后,then函数就会被调用,这时候resolve()方法给传递的值为1,因此执行then的回调函数后,值变为1;
我们再来看看当一个promise对象变为确定状态(FulFiled)的时候,他们后面的promise对象是否还在运行呢?我们继续看如下代码运行:
var runPromise = new Promise(function(resolve){
setTimeout(function(){
console.log(1);
resolve(2);
},500);
});
var runPromise2 = new Promise(function(resolve){
setTimeout(function(){
console.log(3);
resolve(4);
},1000);
});
// 第一个promise变为resolve后程序停止
Promise.race([runPromise,runPromise2]).then(function(value){
console.log(value);
});
如上代码是使用定时器调用的,上面是2个promise对象,我们看到第一个promise对象过500毫秒后加入到执行队列里面去,如果执行队列没有其他线程在运行的时候,就执行该定时器,所以第一次打印1,然后调用resolve(2); 接着调用promise.race方法,该方法只要有一个变为成功状态(FulFiled)的时候,程序就会停止,因此打印出2,同时后面的promise对象接着执行,因此打印出3,但是由于promise.race()该方法已经停止调用了,所以resolve(4)不会有任何输出;因此最后输出的是1,2,3;
由此我们得出结论,当一个promise对象变为(FulFilled)成功状态的时候,后面的promise对象并没有停止运行。
Deferred和Promise的关系
Deferred 包含 Promise;
Deferred具备Promise的状态进行操作的特权方法;
下面我们来看看使用promise来实现deferred;如下代码:
function Deferred(){
this.promise = new Promise(function(resolve,reject){
this._resolve = resolve;
this._reject = reject;
}.bind(this));
}
Deferred.prototype.resolve = function(value) {
this._resolve.call(this.promise,value);
};
Deferred.prototype.reject = function(reason) {
this._reject.call(this.promise,reason);
};
function getURL(URL){
var deferred = new Deferred();
var req = new XMLHttpRequest();
req.open('GET',URL,true);
req.onload = function(){
if(req.status === 200) {
deferred.resolve(req.responseText);
}else {
deferred.reject(new Error(req.statusText));
}
};
req.onerror = function(){
deferred.reject(new Error(req.statusText));
};
req.send();
return deferred.promise;
}
var URL = 'http://127.0.0.1/promise/promise.php';
getURL(URL).then(function onFulfilled(value){
console.log(value);
});
其中promise.php代码输出的是一个json的数据,代码如下:
<?php
$data = json_decode(file_get_contents("php://input"));
header("Content-Type: application/json; charset=utf-8");
echo ('{"id" : ' . $data->id . ', "age" : 24, "sex" : "boy", "name" : "huangxueming"}');
?>
最后执行打印console的出来是:
{“id” : , “age” : 24, “sex” : “boy”, “name” : “huangxueming”}
使用promise封装deferred的方法,无非就是使用promise对象中的resolve和Reject等调用方法,下面我们再来看看使用promise对象对ajax请求的封装如下:
function getURL(URL){
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
var URL = 'http://127.0.0.1/promise/promise.php';
getURL(URL).then(function onFulfilled(value){
console.log(value);
});
上面分别两种方式使用promise对象实现ajax请求的封装对比如下:
Deferred那种方式不需要将promise代码括起来。
Promise代表了一个对象,这个对象的状态现在还不确定,但是未来一个时间点它的状态要么变为正常值(FulFilled),要么变为异常值(Rejected);而Deferred对象表示了一个处理还没有结束的这种事实,在它的处理结束的时候,可以通过Promise来取得处理结果。