Promise:对异步操作的状态管理
基本使用方法
// resolve 成功
// reject 失败
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('ES6之Promise');
// resolve()
// reject()
// if(){
// resolve()
// } else {
// reject()
// }
}, 1000);
}).then(() => {
console.log('成功1');
}, () => {
console.log('失败1');
})
then(()=>{// success},()=>{// fail})then接收2个回调作为参数,第一个是成功回调,第二个是失败回调,第一个参数必传,第二个参数可以省略- Promise的参数是一个回调,回调的的2个参数是函数,第一个参数resolve表示回调成功后执行的方法,第二个参数表示回调失败后执行的方法,当调用resolve的时候会进入then的第一个方法里,当调用reject的时候会进入then的第二个方法里
- 如果异步的结果成功了,需要手动的调用下
resolve(),这样才能进入下一个then的成功回调 - 如果异步的结果失败了,需要手动的调用下
reject(),这样才能进入下一个then的失败回调 - 如果后面的异步操作想得到上一步的异步操作的结果,成功了就将成功的结果放到成功回调
resolve(成功的结果)的参数中,在then的成功回调中用参数接收,如果失败了就将失败的结果放到失败回调reject(失败的结果)的参数中,在then的失败回调中参数接收
Promise与异步的关系
Promise和异步没有任何关系!
Promise中也可以放同步的代码,eg:
let p = new Promise((resolve,reject) => {
console.log(1)
resolve() // 如果想进入then,必须手动调用then/reject
})
console.log(2);
p.then(res => {
console.log(3);
}) // 输出结果: 1 2 3
只不过大部分时候,Promise都会结合异步的操作去使用,否则就失去了Promise的意义了
new Promise里面的代码会被立即执行
then相当于是Promise的微任务,而正常的代码是一个宏任务,JS中会先进行宏任务再执行微任务
Promise的三种状态
三种状态
Promise中只有三种状态:当 new Promise的时候它的状态是pending(进行中);如果异步执行结果成功就调用resolve(),状态变为fulfiled(已成功);如果异步执行结果失败就调用reject(),状态变为rejected(已失败)
Promise的这三种状态并不受外界的影响,只有当前异步处理的结果resolve/reject,才会决定当前Promise到底处于哪种状态,其他的操作都没办法改变它,它的状态是不可逆的
eg:
let p1 = new Promise((resolve,reject)=>{
resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(2)
}, 1000);
})
let p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
reject(3)
}, 1000);
})
console.log(p1); // resolved
console.log(p2); // pending
console.log(p3); // pending
// p2、p3 resove或者reject改变状态之后的结果
setTimeout(() => {
console.log(p2);
}, 2000);
setTimeout(() => {
console.log(p3);
}, 2000);
// then中拿到Promise的结果
p1.then(res => {
console.log(res);
})
p2.then(res => {
console.log(res);
})
p3.catch(err => {
console.log(err);
})
为什么Promise外面的状态和里面的状态不一致?
为什么输出的p2外面的Promise的状态是pending,而里面的状态又是fulfiled?
因为当我们写了new Promise(...)之后会立即执行它里面的代码,但是此时还没有resolve(),resolve()是在1秒后执行,因此转态先是pending,1秒后状态变成fulfiled
其实当我们在1秒之前打开,结果里p2的Promise的状态也显示是pending状态,注意不管是外面的还是里面的Promise的状态都是同一个P2的状态,我们只需要在意外面的状态就行
p2、p3 resove或者reject改变状态之后的结果
如果我们想看p2、p3 resove或者reject改变状态之后的结果,不妨加个定时器,2秒后,p2的resole已经执行,Promise的状态已被改变。2秒后,p3的reject已经执行,Promise的状态也已被改变。
then中拿到Promise的结果
失败的回调函数可以放到then的第二个参数中,或者使用catch,catch相当于只有失败回调的then
为什么Promise的状态是不可逆的?
let p = new Promise((resolve, reject) => {
resolve(1)
reject(2)
})
p.then(success => {
console.log(success);
},err => {
console.log(err);
})
//等价于下面的写法
p.then(success => {
console.log(success);
}).catch(err => {
console.log(err);
})
结果是只输出1,2并没有被输出
为什么这样?因为Promise就有“承诺/一诺千金”的意思,当前的Promise的状态如果由pending,经过resolve()变成fulfiled,那么状态就已经被“凝固”了,即使后面再调用reject那么状态也不可逆、不可被改变
使用Promise改造Callback Hell 的例子
原始的方式
1. 错误的写法
function ajax(url, callback) {
// 1. 创建XMLHttpRequest对象
var xtmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
// 兼容早期浏览器
} else {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2. 发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3. 接收服务端相应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
callback(obj);
}
}
}
new Promise((resolve,reject) => {
ajax('static/a.json',res => {
console.log(res);
resolve()
})
}).then(res => {
console.log('a读取成功');
new Promise((resolve,reject)=>{
ajax('static/b.json',res => {
console.log(res);
resolve()
})
})
}).then(res => {
console.log('b读取成功');
})
看下运行的结果:
结果输出的顺序好像不对,不应该:a的结果 -> a读取成功 -> b的结果 -> b读取成功吗?
第二个then原本是想对第一个then返回的Promise进行链式操作,接着then,但是由于第一个then中的并没有返回值(没有写return),因此第二个then相当于对一个空的Promise对象进行then的操作
如果想进行链式操作,我们需要将Promise对象return出去(return出去的Promise对象相当于得到了一个新的Promise对象),以供后面的then继续的then,进行链式操作
2.修正后的写法
function ajax(url, callback) {
// 1. 创建XMLHttpRequest对象
var xtmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
// 兼容早期浏览器
} else {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2. 发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3. 接收服务端相应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
callback(obj);
}
}
}
new Promise((resolve,reject) => {
ajax('static/a.json',res => {
console.log(res);
resolve()
})
}).then(res => {
console.log('a读取成功');
return new Promise((resolve,reject)=>{
ajax('static/b.json',res => {
console.log(res);
resolve()
})
})
}).then(res => {
console.log('b读取成功');
})
输出的结果的顺序也符合预期了
3. 再把读取c.json加进来
function ajax(url, callback) {
// 1. 创建XMLHttpRequest对象
var xtmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
// 兼容早期浏览器
} else {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2. 发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3. 接收服务端相应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
callback(obj);
}
}
}
new Promise((resolve,reject) => {
ajax('static/a.json',res => {
console.log(res);
resolve()
})
}).then(res => {
console.log('a读取成功');
return new Promise((resolve,reject)=>{
ajax('static/b.json',res => {
console.log(res);
resolve()
})
})
}).then(res => {
console.log('b读取成功');
return new Promise((resolve,reject)=>{
ajax('static/c.json',res => {
console.log(res);
resolve()
})
})
}).then(res => {
console.log('c读取成功');
})
输出的结果:
看下之前回调深渊的写法:
ajax('static/a.json',res =>{
console.log(res) // {a: '我是A'}
ajax('static/b.json',res => {
console.log(res); // {b: '我是B'}
ajax('static/c.json',res => {
console.log(res); // {c: '我是C'}
})
})
})
上面的写法层层嵌套
使用Promise改写之后,就将之前层层嵌套的方式,改为扁平式的,代码更利于阅读和维护,也更好的管理失败后做什么,失败后做什么
但是可能会产生疑问:这样代码变得更多了
改造的方式
我们发现有很多重复的部分,每次都是返回一个Promise对象,在Promise对象中进行Ajax请求,我们不妨封装成函数
function ajax(url, callback) {
// 1. 创建XMLHttpRequest对象
var xtmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
// 兼容早期浏览器
} else {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2. 发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3. 接收服务端相应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
callback(obj);
}
}
}
function getPromise(url) {
return new Promise((resolve, reject) => {
ajax(url, res => {
resolve(res)
})
})
}
getPromise('static/a.json')
// then里面的结果res都是通过函数getPromise中resolve传递过来的
.then(res => {
console.log(res);
return getPromise('static/b.json')
}).then(res => {
console.log(res);
return getPromise('static/c.json')
}).then(res => {
console.log(res);
})
这样代码就变得更加的扁平化、语义化,便于阅读和对结果进行管理,这就是Promise的好处
再加入对失败结果的处理吧
function ajax(url, successCallback, failCallback) {
// 1. 创建XMLHttpRequest对象
var xtmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
// 兼容早期浏览器
} else {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2. 发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3. 接收服务端相应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
successCallback && successCallback(obj);
} else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
failCallback && failCallback(xmlhttp.statusText)
}
}
}
function getPromise(url) {
return new Promise((resolve, reject) => {
ajax(url, res => {
resolve(res)
}, err => {
reject(err)
})
})
}
getPromise('static/aa.json')
.then(res => {
console.log(res);
return getPromise('static/b.json')
}, err => {
console.log(err);
}).then(res => {
console.log(res);
return getPromise('static/c.json')
}).then(res => {
console.log(res);
})
看下运行结果:
- Promise中不会因为第一个异步操作的结果失败而影响第二个、第三个异步操作,第二个、第三个异步操作并没有停止执行,也执行了
- 为什么输出undefined? ==> Promise中发起的ajax请求的路径'static/aa.json'并不存在,因此第一个then会进入失败回调,然而第一个then中的失败回调并没有返回任何的Promise对象,相当于返回一个空的Promise对象,而第二个then是对第一个then失败结果返回的Promise进行then,所以第二个then中输出undefined。或者说第二个then中的res是第一个then的成功回调的结果,但是由于第一个then并没有执行成功回调而是执行失败回调,所以第二个then中的成功回调res结果是undefined
- 为什么还能正确的输出
{c:'我是C'}? ==>因为它的上一个then中对'static/c.json'发起了ajax请求,并成功的返回了Promise对象(返回了成功函数的结果)
aa.json读取失败但是不要影响b.json的读取
aa.json读取失败后,也继续返回Primise对象,对'static/b.json'发起ajax请求
getPromise('static/aa.json')
.then(res => {
console.log(res);
return getPromise('static/b.json')
}, err => {
console.log(err);
// a.json读取失败但是不要影响b.json的读取
return getPromise('static/b.json')
}).then(res => {
console.log(res);
return getPromise('static/c.json')
}).then(res => {
console.log(res);
})
输出的结果
不对失败的结果进行单独的处理,而是进行统一的处理
function ajax(url, successCallback, failCallback) {
// 1. 创建XMLHttpRequest对象
var xtmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
// 兼容早期浏览器
} else {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2. 发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3. 接收服务端相应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
successCallback && successCallback(obj);
} else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
failCallback && failCallback(xmlhttp.statusText)
}
}
}
function getPromise(url) {
return new Promise((resolve, reject) => {
ajax(url, res => {
resolve(res)
}, err => {
reject(err)
})
})
}
getPromise('static/aa.json')
.then(res => {
console.log(res);
return getPromise('static/b.json')
}).then(res => {
console.log(res);
return getPromise('static/c.json')
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
运行的结果:
这样如果出现了失败就直接进入catch,错误之前的then就不会再进入
Promise静态方法
Promise.resolve()、Promise.reject()
有的时候我们并没有Promise对象,但是我们又想调用then方法,就可以使用Promise.resolve()或者Promise.reject(),把它包装成成功的状态或者失败的状态
基本使用方法
Promise.resolve()它表示成功的状态,它返回一个Promise对象
Promise.reject()它表示失败的状态,它返回一个Promise对象
let p1 = Promise.resolve('success')
console.log(p1);
p1.then(res => {
console.log(res);
})
let p2 = Promise.reject('fail')
console.log(p2);
p2.catch(err => {
console.log(err);
})
实际应用场景
eg: 如果失败的返回值是字符串,而不是一个Promise对象,但是也想使用then方法该怎么办?
function foo(flag) {
if(flag) {
return new Promise(resolve => {
// 异步操作
resolve('success')
})
} else {
return 'fail' // Uncaught TypeError: foo(...).then is not a function
}
}
foo(false).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
使用Promise.reject()改进
function foo(flag) {
if(flag) {
return new Promise(resolve => {
// 异步操作
resolve('success')
})
} else {
// return 'fail'
return Promise.reject('fail')
}
}
foo(false).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // fail
})
Promise.all()
接收数组作为参数,数组中的每个值都是一个Promise对象,它后面的then的成功函数的参数,是数组中每个Promise对象resolve返回值组成的数组
基本使用
eg: 场景1
3个Promise对象,里面都有异步操作,现在想让这3个异步操作执行完成之后再去做另外一件事
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(1);
resolve('1成功')
}, 1000);
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(2);
resolve('2成功')
}, 2000);
})
let p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(3);
resolve('3成功')
}, 3000);
})
Promise.all([p1,p2,p3]).then(res => {
console.log(res);
})
如果3个Promise中有一个异步的结果是失败的呢?
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(1);
resolve('1成功')
}, 1000);
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(2);
reject('2失败')
}, 2000);
})
let p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(3);
resolve('3成功')
}, 3000);
})
Promise.all([p1,p2,p3]).then(res => {
console.log(res);
},err => {
console.log(err);
})
从上图可以发现只要Promise.all([p1,p2,p3])中一个Promise的结果是失败的,就认为这个结果是失败的,代码直接跳到then的失败处理函数中,只有当这3个Promise的结果都是成功的,才会走到then的成功处理函数中
实际应用场景
上传3张图片,但是服务器给的接口每次只能上传一张图片,当三张图片上传完,给个提示图片上传完成,但是我们怎么知道什么时候图片上传完成?(上传图片的过程也是异步的过程)
// 伪代码
let imgArr = ['1.jpg','2.jpg','3.jpg']
let promiseArr = []
imgArr.forEach(item => {
promiseArr.push(new Promise((resolve,reject) => {
// 图片上传的异步操作
resolve()
}))
})
Promise.all(promiseArr).then(res => {
console.log('图片全部上传完成');
})
Promise.race()
基本使用
接收数组作为参数,数组中的每个值都是一个Promise对象。它和Promise.all()不同,只要上面例子中3个Promise的结果中只要找到第一个是成功的,那么就认为整个结果是成功的,在then的第一个成功回调中就可以拿到结果
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(1);
resolve('1成功')
}, 1000);
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(2);
reject('2失败')
}, 2000);
})
let p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(3);
resolve('3成功')
}, 3000);
})
Promise.race([p1,p2,p3]).then(res => {
console.log(res);
},err => {
console.log(err);
})
如果上面例子中3个Promise的结果中只要找到第一个是失败的,那么就认为整个结果是失败的,在then的第二个失败回调中就可以拿到结果
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(1);
reject('1失败')
}, 1000);
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(2);
resolve('2成功')
}, 2000);
})
let p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(3);
resolve('3成功')
}, 3000);
})
Promise.race([p1,p2,p3]).then(res => {
console.log(res);
},err => {
console.log(err);
})
实际应用场景
eg: 当前页面需要加载图片,图片可能加载成功也可能加载失败,如果加载失败就给用户一个提示图片加载失败,给图片加载设置一个超时时间2秒,如果超过2秒没有加载出来就给提示,如果加载出来就展示图片
// 伪代码
function getImge() {
return new Promise((resolve, reject) => {
let img = new Image()
img.src = 'https://www.imooc.com/static/img/index/logo2020.png'
img.onload = function () {
resolve(img.src)
}
})
}
function timeout() {
return new Promise((res, reject) => {
setTimeout(() => {
reject('图片加载超时')
}, 6000);
})
}
Promise.race([getImge(),timeout()]).then(res => {
console.log(res);
},err => {
console.log(err);
})
总结:Promise的强大之处不仅在于处理回调地狱的问题,Promise也可以处理多个并发的请求,获取多个并发请求的数据 - Promise可以对异步的状态进行管理