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就不会再进入
原文链接:https://juejin.cn/post/7234058546572951612 作者:仗剑走天涯_