回调地域
存在异步任务的代码,不能保证能按照顺序执行,如果我们需要代码顺序执行,要怎么写呢
setTimeout(function () { //第一层
console.log(111);
setTimeout(function () { //第二程
console.log(222);
setTimeout(function () { //第三层
console.log(333);
}, 1000)
}, 2000)
}, 3000)
这种回调函数的层层嵌套,就叫做回调地狱。回调地狱会造成代码可复用性不强,可阅读性差,可维护性(迭代性差),扩展性差等等问题。
异步困境
-
众所周知,js是单线程的,耗时操作都是交给浏览器来处理,等时间到了从队列中取出执行,设计到事件循环的概念,笔者也分享过,可以看以下,理解了可以更好的理解
promise。 -
我以一个需求为切入点,我模拟网络请求(异步操作)
- 如果网络请求成功了,你告知我成功了
- 如果网络请求失败了,你告知我失败了
傻瓜做法
function requestData(url) {
setTimeout(() => {
if (url === 'iceweb.io') {
return '请求成功'
}
return '请求失败'
}, 3000)
}
const result = requestData('iceweb.io')
//先执行完了 再执行的异步 所以输出了undefined
console.log(result) //undefined
首先当我执行requestData函数,开始执行函数。遇到了异步操作不会阻塞后面代码执行的,因为js是单线程的,所以你写的return成功或者失败并没有返回给requestData,那我这个函数中,抛开异步操作,里面并没有返回值,所以值为undefined。
早期正确做法
function requestData(url, successCB, failureCB) {
setTimeout(() => {
if (url === 'iceweb.io') {
successCB('我成功了,把获取到的数据传出去', [{name:'ice', age:22}])
} else {
failureCB('url错误,请求失败')
}
}, 3000)
}
//3s后 回调successCB
//我成功了,把获取到的数据传出去 [ { name: 'ice', age: 22 } ]
requestData('iceweb.io', (res, data) => console.log(res, data), rej => console.log(rej))
//3s后回调failureCB
//url错误,请求失败
requestData('icexxx.io', res => console.log(res) ,rej => console.log(rej))
-
早期解决方案都是传入两个回调,一个失败的,一个成功的。那很多开发者会问这不是挺好的吗?挺简单的,js中函数是一等公民,可以传来传去,但是这样太灵活了,没有规范。
-
如果使用的是框架,还要阅读一下框架源码,正确失败的传实参的顺序,如果传参顺序错误这样是非常危险的。
Promise
promise本身只是一个容器,真正异步的是它的两个回调resolve()和reject()
promise本质 不是控制 异步代码的执行顺序(无法控制) , 而是控制异步代码结果处理的顺序
-
promise对象有三个状态:pending(进行中),fulfilled(已成功),rejected(已失败)
-
如何改变promise的状态:
- resolve(value): 如果当前是 pending 就会变为 resolved
- reject(error): 如果当前是 pending 就会变为 rejected
- 抛出异常: 如果当前是 pending 就会变为 rejected
注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
-
当我们
new一个promise,此时我们需要传递一个回调函数,这个函数为立即执行的,称之为(executor) -
这个回调函数,我们需要传入两个参数回调函数,
reslove,reject(函数可以进行传参)- 当执行了
reslove函数,会回调promise对象的.then函数 - 当执行了
reject函数,会回调promise对象的.catche函数
- 当执行了
2.1立即执行
new Promise((resolve, reject) => {
console.log(`executor 立即执行`)
})
解决回调地域
1.Ptomise链式解构
缺点:
写多了也相当于一种回调地域
function fn(){
return new Promise(function (resolve, reject) {
// 第一个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为成功
// 第二个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为失败
const timer = Math.ceil(Math.random() * 3000);
console.log("买水");
setTimeout(() => {
if (timer > 2500) {
reject('超时,所以买水失败');
} else {
resolve('超时,所以买水失败');
}
}, timer);
});
}
//链式调用
//当你在第一个then里边返回一个新的promise对象
//然后你可以在第一个then的候面再次书写一个then
fn().then((str) => {
console.log("promise 1成功");
return fn()
}).then((str) => {
console.log("promise 2成功");
return fn()
}).then((str) => {
console.log("promise 3成功");
}).catch(() => {
console.log("promise 失败");
});
2. async/await
一句话概括: 它就是 Generator 函数的语法糖,也就是处理异步操作的另一种高级写法
async语法如下:
async是异步的意思,await是等待的意思,async是声明一个异步函数,await是等待异步函数执行完毕;
- 函数前面使用
async修饰,将函数变为异步 - 函数内部,promise操作使用
await修饰
-
await 后面是promise对象, 左侧的返回值就是这个promise对象的then方法中的结果
-
await必须要写在async修饰的函数中,不能单独使用,否则程序会报错
await 关键字
async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。
await针对所跟不同表达式的处理方式:
-
Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
-
非 Promise 对象:直接返回对应的值
-
异步函数中可以使用
await关键字,普通函数不行-
这个promise状态变为fulfilled才会执行
await后续的代码,所以await后面的代码,相当于包括在.then方法的回调中,如果状态变为rejected,你则需要在函数内部try catch,或者进行链式调用进行.catch操作
-
使用anync与awit解决回调地域
存在问题
promise状态变为fulfilled才会执行await后续的代码,所以await后面的代码,相当于包括在.then方法的回调中,如果状态变为rejected
function fn(){
return new Promise(function (resolve, reject) {
// 第一个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为成功
// 第二个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为失败
const timer = Math.ceil(Math.random() * 3000);
console.log("买水");
setTimeout(() => {
if (timer > 2500) {
reject('超时,所以买水失败');
} else {
resolve('成功');
}
}, timer);
});
}
newfn()
async function newfn(){
const r1 = await fn()
cosole.log('第一次',r1)
const r2 = await fn()
console.log('第一次',r2)
const r3 = await fn()
console.log('第一次',r3)
}
异步函数的异常处理
1.`try catch
function fn(){
return new Promise(function (resolve, reject) {
// 第一个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为成功
// 第二个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为失败
const timer = Math.ceil(Math.random() * 3000);
console.log("买水");
setTimeout(() => {
if (timer > 100) {
reject('超时,所以买水失败');
} else {
resolve('成功');
}
}, timer);
});
}
newfn()
async function newfn(){
try{ //try抛出错误之后,就不会执行这条语句
const r1 = await fn()
console.log('第一次',r1)
const r2 = await fn()
console.log('第二次',r2)
const r3 = await fn()
console.log('第三次',r3)
}catch(error){
console.log(error)//catch语句能捕获到错误信息
}
console.log('我是一个字符串')//try接收到报错后 不会影响后续输出
}
封装Promise 待写
promise对象方法
finally
每次都会执行一次
//1.promise 对象上的方法
const res = fn()
res.then(()=>{
console.log('成功')
}).catch(()=>{
console.log('失败')
}).finally(()=>{
console.log('每一次都会执行(不会考虑成功还是失败)')
})
promise构造函数方法
all 可以实现并行处理
必须都成功才算成功
//all 必须都成功才算成功
Promise.all([fn(),fn()]).then(()=>{
console.log('都成功')
}).catch(()=>{
console.log('有一个失败就是失败')
})
race
执行速度最快的是对的 就是对的
//race 执行速度最快的是对的 就是对的
Promise.race([fn(),fn()]).then(()=>{
console.log('都成功')
}).catch(()=>{
console.log('有一个失败就是失败')
})