ES6---Promise对象
1. 为什么要使用Promise
当我们使用js的异步调用时通常习惯使用回调函数,这样的代码简单便于理解,但是当回调嵌套的时候就会造成代码混乱,不好梳理,比如:
function fun1(arg1,function(){
// dosomething
})
这样简单的嵌套自然是没问题,还便于理解。但是当有多层嵌套的时候:
我们只知道Promise是最大的好处是为了避免“回调地狱”,就是多层的回调
function fun1(arg1,function(data1){
function fun1(arg2, function(data2){
function fun1(arg3, function(data3){
//.....
})
})
})
像上面这样,三层嵌套就已经很麻烦了,更何况更多层的嵌套,所以为了让回调更明显,代码更容易理解,使用Promise可以很优雅的解决这个问题:
var p = new Promise(function(resolve,reject){
}).then(function(data){
}).then(function(data){
}).then……
上面就是Promise的基本用法,Promise接受一个回调函数,回调函数接收两个参数,resolve(将Promise的状态从pending变为fulfilled,在异步操作成功时调用,并将异步操作的结果传递出去)、reject(将Promise的状态从pending变为rejected,在异步操作失败时调用,将异步操作的错误作为参数传递出去)这两个都是函数,表示成功和失败的处理函数。then中接受的是上一次回调返回的结果,所以这样的链式调用就可以完全清晰的实现多层调用。
2. Promise对象的特点
- 对象的状态不受外界的影响,Promise有三种状态:Pending(进行中)、fulfilled(已成功)、rejected(失败),只用异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个操作。
-== 一旦状态改变之后就不会再改变==。任何时候都可以得到这个结果。状态变化只有两种可能:从pending到fulfilled和从pending到rejected。只要改变就已经定型了。 - 一旦创建就无法取消,一旦新建就会立即执行
- 如果不设置回调函数,它的内部错误就不会反映到外部。
- 当处于pending状态时,无法判断进展到哪一阶段(刚开始还是快完成)。
3.Promise对象的基本用法
Promise打印出来的结果如下:
从上图可以看到Promise.prototype上有catch、then、constructor方法。所以这几个方法可以被实例继承。
Promise自身会有Promise.all()、Promise.race()、Promise.resolve()、Promise.reject()一些常用的方法。
Promise.prototype.then()
let promise = new Promise(function(resolve,reject){
console.log("promise");
resolve();
});
setTimeout(function(){
console.log("setTimeout");
},0)
promise.then(function(){
console.log("resolved");
})
console.log("hi");
// promise hi resolved setTimeout
then方法的作用是为Promise实例添加状态改变时的回调函数。上面的代码很好的验证了,promise是创建之后立即执行,then方法指定的脚本在当前的所有同步任务完成之后再执行,setTimeout是在下一轮“时间循环”开始时执行,then在本轮事件循环结束时执行。
Promise.prototype.catch()
当Promise对象执行成功时使用的是resolve回调函数,进而在then方法中进一步处理,当promise对象失败时有两种方法可以处理:
- 在then方法接受第二个函数参数,用来处理错误。(不推荐)
let promise = new Promise(function(resolve,reject){
reject();
});
promise.then(function(){
console.log("resolved");
},function(){
console.log("rejected")
})
输出 rejected
- 在catch中进行处理。(推荐)
let promise = new Promise(function(resolve,reject){
reject();
});
promise.then(function(){
console.log("resolved");
}).catch(function(){
console.log("catch the reject")
})
输出 catch the reject
reject的作用就相当于抛出错误,catch或者then的第二个函数参数进行捕获,在resolve之后再抛出错误是没有用的,因为状态一旦发生就无法改变,例如:
let promise = new Promise(function(resolve,reject){
resolve('ok');
throw new Error('test')
});
promise.then(function(value){
console.log("value");
}).catch(function(error){
console.log("error")
})
输出 ok
Promise对象的错误具有冒泡的性质,即所有的错误一直可以向后传递,直到遇到cantch被捕获,也就是说,错误总是会被下一个catch语句捕获。
getJSON('/post/1.json').then(function(post){
return getJSON(post.commentURL);
}).then(function(){
//some code
}).catch(function(error){
// 处理前面产生的错误
});
Promise对象若没有指定错误处理,内部错误不会退出进程或终止脚本执行,也就是说promise对象的内部错误不会影响外部代码的执行。
let promise = new Promise(function(resolve,reject){
resolve();
});
promise.then(function(){
console.log("resolved");
//下面一行会报错,因为x没有声明
x+2;
})
setTimeout(function() {
console.log("我出现在x+2之后")
},3000)
执行结果如下:
上面的代码,浏览器遇到x+2未声明抛出错误,但是setTimeout中的字符串在3秒后依然可以打印出来。
Promise.resolve()
作用:将现有对象转化为一个Promise对象。
将一个对象转化为Promise对象分为四种情况:
- 参数是Promise的实例,不做任何改变。
- 参数是一个对象,且含有then方法(简称thenable对象);
var thenable = {
then:function(resolve,rejected){
resolve(42);
}
}
let p1 = Promise.resolve(thenable);
p1.then(function(data){
console.log(data);//42
})
thanable的then方法执行后,对象p1的状态就变为resolved,立即执行then。
当然Promise.resolve()也可以生成状态为rejected的promise对象,上面只需要将resolve改为reject,再在p1.then后面加上.catch用于捕获错误就可以啦!
- 参数不具有then方法,或者说根本就不是对象的时候。
var p = Promise.resolve(“Hello”);
p.then(function(s){
console.log(s);//Hello
})
返回的promise的实例的状态直接就是resolved,所以会执行then方法。并且Promise.resolve()的参数会传递给回调函数。
- 不带有任何参数,用于快速的生成一个Promise对象。
var p = Promise.resolve();
Promise.all()
该方法接受多个Promise实例作为参数,返回一个新的Promise实例。当所有的Promise实例都返回resolve的时候,新的Promise实例的状态是fulfilled,此时p1,p2,p3的返回值组成一个数组传递给新实例的回调函数。当有一个返回的是rejecte的时候,新实例的状态就是rejected。此时第一个返回reject的实例的返回值就会传递给p的回调函数。
var p1 = Promise.resolve("Welcome");
var p2 = "To";
var p3 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve("XIAN");
},1000)
})
上述代码作何调整可以输出 Welcome To XIAN?
Promise.all([p1,Promise.resolve(p2),p3])
.then(function(data){
console.log(data.join(" "));//转化为字符串输出
}).catch(function(e){
console.log(new Error(e));
})
上面的data是Promise对象resolve函数的参数组成的数组,all中的顺序决定了输出的顺序,与其他的因素没有关系。
如果所有实例中用catch方法用于捕获错误,则使用Promise.all方法的catch是不会捕获到的,但是会执行Promise.all中的then方法,为什么?不是说当所有实例都返回的是resolve状态时才会触发Promise.all的then方法么?
因为当某一个实例报错时,使用catch进行错误处理,返回的是一个新的Promise实例,该实例完成catch之后状态也会变为resolve,所以导致Promise.all所有实例的都返回resolve,会触发Promise.all的then。
解决方法就是在实例中不添加catch,那么实例中reject就会触发Promise.all的catch,从而达到Promise.all存在的真正意义。
Promise.race()
与Promise.all一样,接受的是promise实例数组作为参数,新生成一个新的Promise对象。该方法的Promise的对象由第一个返回reject或者resolve的实例的状态决定。可以使用我们经常看到的是图片加载超时什么提示,那么可以使用该方法实现一下。
function preloadImage(path){
return new Promise(function(resolve,reject){
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
})
}
要实现的功能是在五秒之后如果图片加载不出来就提醒图片加载超时。
Promise.race([preloadImage("/images/1.png"),
new Promise(function(resolve,reject){
setTimeout(function() {
reject();
},5000);
})]).then(function(){
alert("图片加载成功!")
}).catch(function(){
alert("图片加载失败!")
})
在五秒中之内先是图片加载,(当然图片加载也有可能失败)还是在五秒后出发setTimeout函数执行reject。这样可以允许图片在五秒之内完成加载给提示。
Promise.reject()
看到这个就会想到Promise.resolve().区别就是:
Promise.resolve()生成的Promise对象可以是rejected状态和resolve状态。
Promise.reject()只能生成状态为rejected的Promise实例。
var arg = "Hello";
var pro1 = Promise.reject(arg);
pro1.then(function(){
console.log("resolved");
}).catch(function(e){
console.log(e) //Hello
})