Promise对象的意义:
说一个浅显的例子,在日常的开发中,在一次异步请求成功之后,拿到请求到的参数,再进行往下的n个操作,有的时候或许还能用到异步请求之后再接下来一个异步请求,下面代码是最直接无脑的写法:
$.ajax({//注意:这是第一个ajax请求
url: '......',
success: function (data) {
$.ajax({//这是第二个ajax请求
// 要在第一个请求成功后才可以执行下一步
url: '......',
success: function (data) {
// ......
}
});
}
});
复制代码
首先从代码颜值上来看,这一层层的嵌套,真的不够优雅。其次,这样的操作会造成第二次请求或许根本就不能进行。 这个时候Promise对象就该闪亮登场了
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
Promise对象有三种状态
1 异步操作”未完成”(pending)
2 异步操作”已完成” (resolved)
3 异步操作”失败” (rejected)
这三种状态的变化途径只有2种
1 异步操作从”未完成”到”已完成”
2 异步操作从”未完成“到”失败”
所以Promise对象的最终结果只有两种:
1 异步操作成功 Promise对象传回一个值,状态变为resolved
2 异步操作失败 Promise对象抛出一个错误,状态变为rejected
另外,Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从pending变为resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
Promise对象有两个特点
①,对象的状态不受外界的影响,Promise有三种状态:Pending(进行中)、fulfilled(已成功)、rejected(失败),只用异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个操作。
②.一旦状态改变之后就不会再改变。任何时候都可以得到这个结果。状态变化只有两种可能:从pending到fulfilled和从pending到rejected。只要改变就已经定型了。
Promise的缺点:
① 一旦创建就无法取消,一旦新建就会立即执行
② 如果不设置回调函数,它的内部错误就不会反映到外部。
③ 当处于pending状态时,无法判断进展到哪一阶段(刚开始还是快完成)。
接下来讲一下Promise的基本语法,先看一下Promise打印出来的结果
从上图可以看到Promise.prototype上有catch、then、constructor方法。所以这几个方法可以被实例继承。
Promise自身会有Promise.all()、Promise.race()、Promise.resolve()、Promise.reject()一些常用的方法。
1.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
!
复制代码
上面的代码很好的验证了,promise是创建之后立即执行,then方法指定的脚本在当前的所有同步任务完成之后再执行,setTimeout是在下一轮“时间循环”开始时执行,then在本轮事件循环结束时执行。
2.Promise.prototype.catch()
当Promise对象执行成功时使用的是resolve回调函数,进而在then方法中进一步处理,当promise对象失败时在那儿进行处理? 有两种方法:
①在then方法接受第二个函数参数,用来处理错误。(不推荐)
② 在catch中进行处理。(推荐)
let promise = new Promise(function(resolve,reject){
reject();
});
promise.then(function(){
console.log("resolved");
},function(){
console.log("rejected")
})
输出 rejected
复制代码
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之后再抛出错误是没有用的,因为状态一旦发生就无法改变。
Promise对象的错误具有冒泡的性质,即所有的错误一直可以向后传递,知道遇到cantch被捕获。 注意:一般尽量不要使用then的第二个函数参数进行错误处理,尽量使用catch进行错误的统一处理。
Promise对象若没有指定错误处理,内部错误不会退出进程或终止脚本执行,也就是说promise对象的内部错误不会影响外部代码的执行。
let promise = new Promise(function(resolve,reject){
resolve();
});
promise.then(function(){
console.log("resolved");
y+2;
})
setTimeout(function() {
console.log("我出现在y+2之后")
},3000)
上面的代码,浏览器遇到y+2未声明抛出错误,但是setTimeout中的字符串在3秒后依然可以打印出来。
复制代码
promise对象的一些方法 我觉得介绍Promise的方法时应该先介绍这个方法,因为后面要用到。 不要觉得它生成的Promise对象的状态直接是resolved。(视情况而定) 该方法的作用是将现有对象转化为一个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();
2. Promise.all()
该方法接受多个Promise实例作为参数,返回一个新的Promise实例。
当所有的Promise实例都返回resolve的时候,新的Promise实例的状态是fulfilled,此时p1,p2,p3的返回值组成一个数组传递给新实例的回调函数。 当有一个返回的是rejecte的时候,新实例的状态就是rejected。此时第一个返回reject的实例的返回值就会传递给p的回调函数。
promise接受的参数都是promise的实例,那么怎样将所有的参数都转化为promise的实例呢?使用上面的Promise.resolve()方法。 为了更好的掌握Promise.all()方法,来做一个例题。
怎样使用Promise的相关知识输出 Welcome To XIAN
var p1 = Promise.resolve("Welcome");
var p2 = "To";
var p3 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve("XIAN");
},1000)
})
复制代码
其实这个题在我讲的这块出现,大家都知道要使用Promise.all方法,还有问题就是他的参数必须都是Promise的实例,p1已经通过Promise.resolve转化成了Promise对象,p2我们再使用它转换一下,p3本身就是Promise对象的实例。
使用下面的代码就完美的解决了:
Promise.all([p1,Promise.resolve(p2),p3])
.then(function(data){
console.log(data instanceof Array)//true
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存在的真正意义。
3.Promise.race() 与Promise.all一样,接受的是promise实例数组作为参数,新生成一个新的Promise对象。所以该方法的参数可以使用Promise.resolve()方法来解决。
该方法的Promise的对象由第一个返回reject或者resolve的实例的状态决定。可以使用我们经常看到的是图片加载超时什么提示,那么可以使用该方法实现一下。
加载图片的函数也使用Promise对象
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。这样可以允许图片在五秒之内完成加载给提示。
4.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 === arg)//true
console.log(e) //Hello
})
复制代码