ES6之前解决异步编程只能使用回调函数或事件,ES6中加入了 Promise
,使得异步编程更加简洁直观和合理
特点
Promise
是一个对象,具有以下两个特点:
- 对象的状态不受外界影响
- 状态一旦改变就不会再变
使用方法
基本使用
ES6中规定,Promise
对象是一个构造函数,于是我们就需要使用new
关键字实例化:
code:
const promise = new Promise((resolve, reject) => {
if (true) {
resolve(successRes);
} else {
reject(errorText);
}
});
Promise
接受一个函数作为参数,该函数的两个参数分别是:resolve
和reject
。其中:
resolve
可以表示异步操作成功时调用
reject
则可以表示异步操作失败时调用
then
Promise
实例生成之后,可以使用then
方法分别指定成功和失败状态的回调函数。例如:
code
let a = 10;
const promise = new Promise((resolve, reject) => {
if (a === 10) {
resolve('成功!');
} else {
reject('失败!');
}
});
promise.then((res) => {
console.log(res); // 成功!
}, (err) => {
console.log(err);
});
let a = 0;
const promise = new Promise((resolve, reject) => {
if (a === 10) {
resolve('成功!');
} else {
reject('失败!');
}
});
promise.then((res) => {
console.log(res);
}, (err) => {
console.log(err); // 失败!
});
当然,then
的第二个参数并不是必须的,大部分时候我们其实都只需要第一个参数(成功),而失败的回调可以放在catch
中去执行。
catch
比如上面返回‘失败’的例子,我们可以使用catch
进行改造:
code
let a = 10;
const promise = new Promise((resolve, reject) => {
if (a === 10) {
resolve('成功!');
} else {
reject('失败!');
}
});
promise.then((res) => {
console.log(res);
}).catch((err) => {
console.log(err); //失败!
});
大部分时候我们都是这样使用的
Ajax实例
我们可以使用Promise
对象实现一个ajax实例,这也是Promise
用处最广的地方:
code
const myAjax = function(data, url) {
const promise = new Promise((resolve, reject) => {
const stateChange = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const myHttp = new XMLHttpRequest();
myHttp.open('GET', url, 'true');
myHttp.onreadystatechange = stateChange;
myHttp.responseType = 'json';
myHttp.setRequestHeader('Accept', 'application/josn');
myHttp.send(data);
});
return promise;
};
myAjax('/uuurl').then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
其他特性
新建就会立即执行
code
const promise = new Promise(function(resolve, reject) {
console.log('我是resolve之前的打印');
resolve();
});
promise.then(function() {
console.log('我是成功的回调的打印');
});
console.log('我是最外层的打印');
// 结果:
// 我是resolve之前的打印
// 我是最外层的打印
// 我是成功的回调的打印
结果和我们想象的一致,先是打印resolve
的console
,因为Promise
一建立就会执行,而后进行的是最外层的打印,那是因为then
指定的回调函数将在当前脚本所有同步任务执行完之后才会执行。
一个异步操作的结果是返回另一个异步操作
通常情况下,reject
函数的参数是Error
对象的实例,表示抛出的错误;而resolve
函数的参数除了正常的值以外,还可能是另一个Promise
实例:
code
const pro1 = new Promise((resolve, reject) => {
});
const pro2 = new Promise((resolve, reject) => {
resolve(pro1);
});
上述代码中,pro1
和pro2
都是Promise
实例,但是pro2
的resolve
将pro1
作为参数,此时pro1
的状态就会传递给pro2
,也就是说,pro1
的状态决定了pro2
的状态。
这一点从以下代码可以很直观的看出来:
code
const pro1 = new Promise((resolve, reject) => {
setTimeout(() => {
return reject(new Error('err'));
}, 3000);
});
const pro2 = new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(pro1);
}, 1000);
});
pro2.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
在这段代码中,pro1
是一个Promise
对象,并且在3秒之后返回Error
的实例err
。pro2
的状态则是在1秒之后改变。由于pro2
返回的是另一个Promise
(pro1
),导致pro2
自己的状态无效了,由pro1
的状态决定pro2
的状态。所以最终输出的结果是:
过了3秒输出
Error:err
调用resolve
或reject
并不会终结Promise
的参数函数的执行
先来看一段代码:
code
const proromise = new Promise((resolve, reject) => {
resolve('ok');
console.log('我是resolve后面的代码');
});
proromise.then(res => {
console.log(res);
});
// 结果:
// 我是resolve后面的代码
// ok
和预想的一样,虽然先调用了resolve('ok');
,但是后面的代码还是会执行,并且会先打印出来,这是因为调用resolve
或reject
并不会终结Promise
的参数函数的执行,而且then
指定的回调函数将在当前脚本所有同步任务执行完之后才会执行。一般调用resolve
和reject
之后Promise
的任务就完成了,所以建议在resolve
和reject
之后加上return
。
参考链接
《ECMAScript 6 入门》——阮一峰
ECMAScript® 2015 Language Specification
ECMAScript® 2016 Language Specification
ECMAScript® 2019 Language Specification