Promise
一、Promise 相关概念
学习参考 廖雪峰 的博客:
在JavaScript的世界中,所有代码都是单线程执行的。
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行,异步执行可以用回调函数实现:
function callback() {
console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');
观察上述代码执行,在Chrome的控制台输出可以看到:
before setTimeout()
after setTimeout()
(等待1秒后)
Done
即:异步操作会在将来的某个时间点触发一个函数调用。
再来一个例子:
回调函数使用频率很高的不能不提 ajax 请求,由于网速等问题,客户端发起一个ajax请求后,得到响应的时间是不固定的。
在ajax的原生实现中,利用了onreadystatechange事件,当该事件触发并且符合一定条件时,才能拿到我们想要的数据,之后我们才能开始处理数据。
var xhr = new XMLHttpRequest();
xhr.open('get','http://localhost:3000/api/one',true);
xhr.send();
xhr.onreadystatechange = function () {
console.log(xhr.readyState);
if(xhr.readyState === 4){ // 代表服务器已经给了响应, 不代表响应成功
if(xhr.status === 200){
console.log(xhr.response);
}
}
}
如果这个时候,我们还需要做另外一个ajax请求,这个新的ajax请求中的一个参数,得从上一个ajax请求中获取,这个时候,得在上一个里面继续进行一个 ajax 请求:
var xhr = new XMLHttpRequest();
xhr.open('get','http://localhost:3000/api/one',true);
xhr.send();
xhr.onreadystatechange = function () {
console.log(xhr.readyState);
if(xhr.readyState === 4){ // 代表服务器已经给了响应, 不代表响应成功
if(xhr.status === 200){
// 在这里进行新的 ajax 请求
var xhr2 = new XMLHttpRequest();
xhr2.open('get','http://localhost:3000/api/one',true);
xhr2.send();
xhr.onreadystatechange = function () {.........}
}
}
}
好,那么如果再来一个呢,这个就是 回调地狱了,为了解决 回调地狱 的问题,让代码更加可读,于是有了 Promise
“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。
二、Promise 原理
Promise对象有三种状态:
- pending: 等待中,或者进行中,表示还没有得到结果
- resolved(Fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行
- rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行
三种状态不受外界影响,状态只能从 pending 变为 resolved 或者 rejected.
function fn(num){
return new Promise(function (resolve,reject) {
if(typeof num == 'number'){
resolve();
}else{
reject();
}
}).then(function () {
console.log('It is a number');
},function () {
console.log('It is not a number');
})
}
fn(1);
fn('hello');
// 打印结果:
/*
It is a number
It is not a number
*/
-
resolve() 和 reject() 的作用就是将状态修改为 resolved 和 rejected
-
.then() 里面两个 function() 参数,第一个是满足条件的resolve,第二个是不满足条件的 reject.分别根据获取到的状态去执行相应的函数
then 方法执行完后会返回一个 promise对象,所以可以进行.then的链式调用,这样就解决了回调地狱的问题
function fn(num) {
return new Promise(function (resolve,reject) {
if(typeof num == 'number'){
resolve();
}else{
reject();
}
})
.then(function () {
console.log('It is a number');
})
.then(null,function () {
console.log('It is not a number');
})
}
fn(123);
fn('hello');
/*
执行结果:
It is a number
It is not a number
*/
三、关于Promise的数据传递
var fn = function (num) {
return new Promise(function (resolve,reject) {
if(typeof num == 'number'){
resolve(num)
}else {
reject('not a number')
}
})
}
fn(1).then(function (num) {
console.log('first:' + num);
return num + 1;
})
.then(function(num) {
console.log('second: ' + num);
return num + 1;
})
.then(function(num) {
console.log('third: ' + num);
return num + 1;
});
/*
执行结果:
first:1
second: 2
third: 3
* */
多个参数传递:
new Promise((resolve, reject)=>{
resolve(1, 2, 232, 212);
}).then((data1, data2, data3, data4)=>{
console.log(data1, data2, data3, data4);
});
四、错误处理
关于错误处理:在使用 Promise 做异步流程控制的时候,关于异常的处理可以通过在最后一个 then 之后设置一个 catch,然后指定一个失败处理函数, 该函数可以捕获前面所有的 Promise 对象本身以及 then 内部的任务错误,当前面任何一个发生异常,直接进入 catch,后续所有的 Promise 包括 then 不再执行
const fs = require('fs');
// 1. 创建一个Promise对象 (一经创建, 立马执行)
readFile(__dirname + '/data/a.txt', 'utf8')
.then((data) => {
console.log(data.toString());
return readFile(__dirname + '/data/b.txt', 'utf8');
})
.then((data) => {
console.log(data.toString());
return readFile(__dirname + '/data/c.txt', 'utf8');
})
.then((data) => {
console.log(data.toString());
}).catch(err => {
console.log(err);
});
function readFile(...args) {
return new Promise((resolve, reject) => {
fs.readFile(...args, (err, data) => {
if (err) {
reject(err);
}
resolve(data);
})
})
}
五、封装ajax
主流框架的ajax一般都是 用Promise封装的。
Promise 封装ajax 示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function xhr(options) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(options.type || 'get', options.url,true);
xhr.onreadystatechange = function () {
if(xhr.readyState === 4){
if( xhr.status === 200){
try {
var response = JSON.parse(xhr.responseText);
resolve(response);
} catch (e) {
reject(e);
}
}else {
reject(xhr.responseText);
}
}
};
xhr.send();
})
}
xhr({
url:'http://ip.taobao.com/service/getIpInfo.php?ip=117.89.35.58',
type:'get',
data:''
}).then((data)=>{
console.log(data);
},function (err) {
console.log(err);
})
</script>
</body>
</html>
Promise封装ajax,待补充一个完整的,上面的这个现在只处理了 get 方式的
六、Promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
var p1 = new Promise(function (resolve, reject) {
resolve('111');
});
var p2 = new Promise(function (resolve,reject) {
resolve('222');
});
var p3 = new Promise(function (resolve,reject) {
reject('333')
});
Promise.all([p1,p2]).then(function (data) {
console.log(data); // 打印 [ '111', '222' ]
}).catch(function (err) {
console.log(err);
});
Promise.all([p1,p2,p3]).then(function (data) {
console.log(data);
}).catch(function (err) {
console.log(err); // 打印:333
})
- Promise.all 得到所有成功的结果的顺序,与传入的Promise对象的数组顺序保持一致。如上面的代码,即使是p2的结果先出来,顺序也不会变。
- 如果我们下一步的动作,需要等待其他的两个结果先出来才能判读是否要往下进行,用Promise.all 来解决。
七、Promise.race
race :种族,赛跑
Promise.race([p1,p2,p3]), 里面的三个任务赛跑,谁先获取到结果谁就跑的快,不管返回的结果是成功还是失败。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('1000秒--success');
console.log(1111);
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('500秒--failed')
console.log(2222);
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log('resolve:'+result)
},function (err) {
console.log('reject:'+err);
}).catch((error) => {
console.log('catch:'+error) // 打开的是 'failed'
});
/*
执行结果:
2222
reject:500秒--failed
1111
* */
- 只要是在数据组里的,都会执行
- 只返回了跑得快的任务的结果