1.Promise是什么?
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
2. 基本用法
Promise对象是一个构造函数,用来生成Promise实例。下面我们来
1.创建一个promise实例:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
- Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
- resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
- reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
2.使用promise实例
Promise实例生成以后,可以用then方法
分别指定fulfilled状态和rejected状态的回调函数,从而接收传递过来的状态。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
- then方法可以接受两个回调函数作为参数。
- 第一个回调函数是Promise对象的状态变为fulfilled时调用
- 第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供
- 这两个函数都接受Promise对象传出的值作为参数。
3. 特点
从上面的用法看,我们可以了解到Promise有如下特点:
- Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有调用了resolve/reject函数来处理结果,才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
举个例子:
var get = new Promise(function (resolve, reject) {
console.log(1);
resolve(2)
console.log(3)
setTimeout(function(){
console.log(5)
},1);
})
get.then(function (value) {
console.log(value);
})
console.log(4);
//输出结果为:1 3 4 2 5
从上面例子我们可以看出,只要使用new Promise,相当于创建一个异步操作。只有调用resolve或者reject才能触发异步操作。所以我们一般在promise内放置异步操作(如请求等),当返回结果,调用resolve或者reject。这里为了简洁明了说明其是异步,所以没有放异步操作,直接调用了resolve函数,从打印结果就可以看出其是异步操作。
- Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
我们更改上面例子:
var get = new Promise(function (resolve, reject) {
console.log(1);
resolve(2)
reject(5)
console.log(3)
})
get.then(function (value) {
console.log(value);
},function(error){
console.log(error);
})
console.log(4);
//输出结果仍为:1 3 4 2
从例子结果可以看出,promise对象要么是fulfilled,要么是rejected,所以不能同时输出
-
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
下面是一个用Promise对象实现的 Ajax 操作的例子。
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
4. Promise.prototype.then()
Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态(已定型,指的是fulfilled状态))的回调函数,第二个参数(可选)是rejected状态的回调函数。
上面我们讲过 回调地域问题,现在我们用promise进行链式调用来解决该问题
const request = url => {
return new Promise((resolve, reject) => {
$.get(url, data => {
resolve(data)
});
})
};
request(url).then(data1 => {
return request(data1.url);
}).then(data2 => {
return request(data2.url);
}).then(data3 => {
console.log(data3);
})
从代码上看,是不是更直观更简洁。
5. Promise.prototype.catch
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。我们分别讲解then函数和catch函数,对比两者的区别:
5.1 then函数的第二个参数:错误回调rejection
1.当调用reject函数时候,执行rejection回调
var get = new Promise(function (resolve, reject) {
reject(a)
})
get.then(null, function (error) {
console.log(error); //1
})
运行结果如下:
2.当程序运行错误,执行rejection回调
var get = new Promise(function (resolve, reject) {
console.log(a);
})
get.then(null, function (error) {
console.log(error);
})
运行结果和上面一样:
由上面2个例子我们可以知道,reject等同于抛出错误。供后面回调函数捕获错误。
2.Promise 内部的错误不会影响到 Promise 外部的代码运行,通俗的说法就是“Promise 会吃掉错误”(js不会因为错误崩溃导致下面代码无法运行)。
var get = new Promise(function (resolve, reject) {
reject(a)
})
console.log(1);
setTimeout(function(){
console.log(2)
},1);
运行结果如下:
从结果可以看出,我们并没有使用回调函数来捕获错误,所以浏览器会报错,但是并没有终止脚本运行。
3.Promise 的状态一旦改变,就永久保持该状态,不会再变了。所以在resolve语句后面,再抛出错误,不会被捕获
var get = new Promise(function (resolve, reject) {
resolve('ok')
console.log(a)
console.log(3)
})
get.then(null,function(error){
console.log(error); //没有执行
})
console.log(1);
setTimeout(function(){
console.log(2)
},1);
// 1 2
从上面例子可以看出,在resolve('ok')后面调用未定义变量a抛出错误,回调函数并没有执行捕获。在这有个奇怪的问题,上面我们在讲解promise特点时候,举过例子证明resolve后面语句也会执行,这里为什么打印结果不是 3 1 2
?
这里需要注意的是:由于resolve后面抛出错误,所以后面语句会被阻止运行(同步异步都会被阻止)。如果没有抛出错误,则正常运行。
5.2 catch函数
上面讲了then函数的第二个参数:我们知道了如果在promise中如果有错,可以使用错误回调rejection进行捕获。那catch呢?和rejection一样,也一样可以捕获。
var get = new Promise(function (resolve, reject) {
reject(a)
})
get.catch(function (error) {
console.log(error); //1
})
运行结果如下:
那么他们两个到底有什么区别呢?唯一的区别就是,如果在 then 的第一个成功回调函数里抛出了异常,catch 能捕获到,而错误回调函数捕获不到
var get = new Promise(function (resolve, reject) {
resolve('ok')
})
get.then(function (value) {
console.log(a);
console.log(value); //因报错被阻止运行
},function(error){
console.log(error); //没有执行捕获,所以浏览器报错
})
所以运行结果如下:
现在我们改成catch就可以捕获
var get = new Promise(function (resolve, reject) {
resolve('ok')
})
get.then(function (value) {
console.log(a);
console.log(value); //因报错被阻止运行
}).catch(function(error){
console.log(error); //可以捕获
})
运行结果如下:
所以一般总是建议,不用rejection回调函数来捕获错误,用catch方法,这样既可以处理 Promise 内部发生的错误,又可以处理成功回调函数中的错误。
有的人可能会好奇,那假若都有错误怎么办?
var get = new Promise(function (resolve, reject) {
console.log(e);
resolve('ok') //报错所以直接进入捕获函数,因为只可能有一种状态
console.log(1); //因报错被阻止运行
})
get.then(function (value) {
console.log(value); //不会调用
}).catch(function(error){
console.log(error);
})
运行结果如下:
Promise对象的状态改变,只可能有一种结果,要么成功,要么失败,不能改变,因为上面直接报错,所以直接进入catch函数中。这里只以catch举例,实际此时用catch或者rejection回调函数都可以。
注意:如果同时用rejection回调函数和catch函数捕获错误,只会优先执行rejection回调函数,不会执行catch函数。
6. Promise.prototype.finally
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的
var get = new Promise(function (resolve, reject) {
resolve(1) //报错所以直接进入捕获函数,因为只可能有一种状态
})
get.then(function (value) {
console.log(value); //不会调用
}).catch(function(error){
console.log(error);
}).finally(function(){
console.log(2);
})
console.log(3);
setTimeout(() => {
console.log(4);
}, 0);
// 3 1 2 4
这里只举了成功状态,失败状态一样,就不举例了。
7. Promise.resolve(value)
该方法的作用是:把value转换成Promise对象。这里说的Promise对象指的是由Promise构造函数生成的实例。
如果传入不同类型的 value 值,返回结果也有区别:
- value本身就是Promise对象,返回结果和入参value相同
let get = new Promise(function (resolve, reject) {
resolve(1)
})
let p1= Promise.resolve(get)
console.log(p1===get); //true
- value是个thenable对象(即该对象有属性名字为then方法),返回结果为Promise对象,其跟随 thenable 对象中的then函数状态(resolved/rejected)
//demo1
let thenable = {
then: function (resolve, reject) {
resolve(1);
}
};
let p1 = Promise.resolve(thenable)
p1.then(function(value){
console.log(value); //1
})
//demo2
let thenable = {
then: function (resolve, reject) {
reject(2);
}
};
let p1 = Promise.resolve(thenable)
p1.then(null,function(error){
console.log(error) //2
})
我们平常用的jquery的ajax就是thenable对象,我们打印看下:
console.log($.ajax());
打印结果如下图:
- value不是具有then方法的对象,或根本就不是对象如字符串、数值等,返回结果为一个resolved状态的 Promise 对象
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
console.log(p)
打印结果如下图:
所以,假如我们用错误回调函数接收,是不会执行的。
const p = Promise.resolve('Hello');
p.then(null,function (error){
console.log(error)
});
console.log(p);
打印结果如下图:
- value不传,返回结果为一个resolved状态的 Promise 对象
const p = Promise.resolve();
p.then(function (s){
console.log(s)
});
console.log(p);
打印结果如下图:
8. Promise.reject(value)
该方法的作用是:把value转换成Promise对象。这里说的Promise对象指的是由Promise构造函数生成的实例。
如果传入不同类型的 value 值,返回结果都为一个rejected状态的 Promise实例(和上面讲的resolve方法不一样)。所以调用该实例会直接调用catch方法或者then第二个参数,其值就是传进去的value值。
- value是promise对象
let get = new Promise(function (resolve, reject) {
resolve(1)
})
let p1= Promise.reject(get)
p1.catch(function(value){
console.log(value===get); //true
})
注意,如果value为promise对象,该对象里面不能用reject或者语法错误,具体原因暂不清楚。
let get = new Promise(function (resolve, reject) {
reject(1)
})
let p1= Promise.reject(get)
p1.catch(function(value){
console.log(value===get);
})
- value是个thenable对象
let thenable = {
then: function (resolve, reject) {
resolve(1);
}
};
let p1 = Promise.resolve(thenable)
p1.catch(function(value){
console.log(value===thenable); //true
})
- value不是具有then方法的对象,或根本就不是对象如字符串、数值等
const p = Promise.reject('Hello');
p.catch(function (s){
console.log(s)
}); //Hello
- value不传
let p1 = Promise.reject()
p1.catch(function(value){
console.log(value);
})
console.log(1)
//1 undefined
注意,如果不传并且不catch捕获,会报错。比如下面代码:
let p1 = Promise.reject()
console.log(1)
运行结果如下:
9. Promise.all(arr)
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。参数为数组。用法如下:
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用之前讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
p的状态由p1、p2、p3决定,分成两种情况。
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled。此时p1、p2、p3的返回值组成一个数组,传递给p的then回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的错误回调函数。
10. Promise.race(arr)
Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。参数为数组。用法如下:
const p = Promise.race([p1, p2, p3]);
上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用之前讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
和all方法不同的是:
- 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
参考来源: