目录
Promise对象
1、Promise是什么?
Promise的中文翻译为”承诺“,在这里你可以将他理解为在未来某一特定时间点承诺返回数据给你。
ECMAscript 6 原生提供了 Promise 对象。Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。
这样子讲还不能够了解Promise,不妨直接在控制台打印出Promise看看它到底是什么?
Promise本身是一个构造函数,身上有all、reject、resolve等方法,原型上有then、catch等方法。Promise构造函数new出来的对象就叫Promise对象。接下来让我们来看一下Promise对象的特点和使用。
2、Promise的特点
①对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
- pending: 初始状态,不是成功或失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,表示其他手段无法改变。
②一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Fulfilled和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。
//可能你现在还不能理解这段代码,建议看完这一章节再回头看这里
var p = new Promise(function(resolve, reject) {
resolve(); //此时已经将p的状态改为fulfilled
throw 'error'; //此时再抛错错误,p的状态也不会改变
});
p.catch(function(e) {
console.log(e); //因为p的状态凝固为fulfilled,所以catch函数也不会被调用
});
// p最终的状态还是fulfilled
3、Promise的优缺点
优点:
①通过链式调用then和catch回调函数解决回调地狱问题
②可以将异步操作队列化,使任务按照预期的顺序执行,返回符合预期的结果
③Promise对象提供了统一的接口,使得控制异步操作更加容易
缺点:
①无法取消Promise,一旦创建就会执行,无法中途取消
②如果不设置回调函数,Promise内部抛出的错误不会反应到外部
③当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始或者是即将完成)
4、创建一个Promise对象
//使用new调用Promise构造函数创建
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
})
Promise的构造函数接收一个函数类型的参数,并且在这个函数内传入两个参数:resolve和reject,分别表示异步操作执行成功后的回调函数和执行失败后的回调函数,但是使用‘成功’和‘失败’来描述并不准确,因为这里的成功和失败与否,都是由你调用resolve回调和reject回调来决定,按照Promise的特点来讲,resolve是将Promise对象的状态置为fullfiled,reject是将Promise对象的状态置为rejected。
5、Promise对象的then方法
function doAsync(){
var p = new Promise(function(resolve, reject){
//通过定时器执行一个异步操作
setTimeout(function(){
console.log('执行完成');
resolve('你想输出的东西');
}, 2000);
})
return p; //返回一个promise对象
}
//调用doAsync()函数
doAsync()
还记得一开始在控制台输出的Promise里面有的then和catch方法吗?现在就来讲解一下它的强大之处。
//因为doAsync函数返回的是一个Promise对象,所以可以直接在函数后面接着调用then方法,then接收一个函数类型的参数,并且这个函数的参数就是我们在doAsync中调用resolve时传的参数。
doAsync().then(function(data){
console.log(data)
})
//运行这段代码,两秒后输出‘执行完成’紧接着输出‘你想输出的东西’
这个时候你应该有所领悟了,这里的then函数就相当于我们平时使用的回调函数,能够在doAsync的异步任务执行完后被执行,resolve和reject就是向then这个函数传递数据和改变Promise对象的状态。
6、通过Promise对象解决回调地狱问题
看到这里小伙伴们不禁会有些疑惑,那这个Promise和回调函数不是有着同样的作用,为什么还要费劲使用Promise呢?
那么问题来了,如果有多层回调怎么办?正如上面讲的回调地狱问题,代码可读性差且不易于维护。这时我们就可以通过Promise对象的链式调用then函数去解决这个问题。
//这里我们使用promise对象的链式调用then函数解决回调地狱章节的第二个例子
//假设业务开发中有三个接口,每个接口都依赖于前一个接口的返回值才能调用
function request1(){
var p = new Promise(function(resolve, reject){
request({
url : 'url1',
data : 'data1',
success(res1){
resolve(res1) //将第一个接口调用成功的返回值交给resolve函数
},
error(err1){
reject(err1) //将第一个接口调用失败的返回值交给reject函数
}
})
})
return p
}
function request2(data){
var p = new Promise(function(resolve, reject){
request({
url : 'url2',
data : data, //将函数接收到的data作为请求参数
success(res2){
resolve(res2) //将第二个接口调用成功的返回值交给resolve函数
},
error(err2){
reject(err2) //将第二个接口调用失败的返回值交给reject函数
}
})
})
return p
}
function request3(data){
var p = new Promise(function(resolve, reject){
request({
url : 'url3',
data : data , //将函数接收到的data作为请求参数
success(res3){
resolve(res3) //将第三个接口调用成功的返回值交给resolve函数
},
error(err3){
reject(err3) //将第三个接口调用失败的返回值交给reject函数
}
})
})
return p
}
request1().then(function(data){ //这里的data就是request1的resolve函数的res1,即第一次请求成功的返回值
return request2(data)
}).then(function(data){ //这里的data就是request2的resolve函数的res2,即第二次请求成功的返回值
return request3(data)
}) //如果后续还有请求,还可以接着then
//这样子写可以使得代码可读性更强,后期更易于维护,并且可以更好地控制异步任务何时执行回调函数
7、深入讲解resolve和reject
接下来我们通过一个案例来深入讲解一下resolve和reject,并补充then和catch的用法。
//定义一个通过定时器异步生成1-10随机数的函数
function getNum() {
var p = new Promise(function (resolve, reject) {
setTimeout(function () {
var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
if (num > 5) {
resolve(num) //如果num大于5,就会将num交给resolve
} else {
reject('数字不对哦') //如果num小于等于5,就会reject一个字符串‘数字不对哦’
}
}, 1000)
})
return p;
}
//接下来我们调用getNum函数
getNum().then(function(res){ //这里的res就是getNum函数里resolve的值
console.log(res) //结合getNum函数,这里表示如果num大于5就会在控制台输出num
}).catch(function(err){ //同理,这里的err就是getNum函数里reject的值
console.log(err) //结合getNum函数,这里表示如果num<=5就会在控制台输出‘数字不对哦’
})
(1)reject的作用相当于抛出错误,catch的作用相当于捕获错误
var p = new Promise(function (resolve, reject) {
throw new Error('test');
});
/*******等同于*******/
var p = new Promise(function (resolve, reject) {
reject(new Error('test'));
});
//用catch捕获
p.catch(function (error) {
console.log(error);
});
/**输出**/
Error: test
(2)接收reject的两种写法:
①在then里面写两个函数:
//then函数接收两个参数
test().then(function(res){
//then里的第一个函数类型的参数表示resolve
},function(err){
//第二个表示reject
})
②在then函数后面接catch函数(推荐使用)
test().then(function(res){
//表示resolve
}).catch(function(err){
//表示reject
})
//为啥推荐使用这种方式
getNum().then(function(res){
console.log(res)
console.log(haha) //此处的haha变量未定义
}).catch(function(err){
console.log(err)
})
//在resolve的回调then()中,我们console.log(haha)的haha变量没有被定义,如果使用了第一种方式去接收reject,程序执行到这里就会报错,终止运行。使用第二种方式的话,报错的信息就会传到catch函数的err参数中,且不会终止程序的运行。
//可以理解为与try/catch语句有类似的功能。
(3)Promise.resolve
Promise.resolve(x)
/*****等价于*****/
new Promise(resolve => resolve(x))
//可以用于快速封装字面量对象或其他对象,将其封装成Promise对象
(4)promise对象的错误(这里的错误指的是reject的抛出,尽管它不一定是个错误,因为是由你定义的reject,我们暂且称它为错误以便于理解)会一直向后传递,直到被捕获。即错误总会被下一个catch
所捕获。then
方法指定的回调函数,若抛出错误(这个错误指的是真的出错),也会被下一个catch
捕获。catch
中也能抛错,则需要后面的catch
来捕获。