这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
一、早期代码困境
- 由于Js是单线程的,耗时操作都是交给浏览器处理(浏览器维护一个队列)
- 异步任务不会占用Js线程,因为其队列是浏览器维护的,当一个异步任务执行完毕以后,会通知js主线程,做处理
早期的做法是通过 回调函数 处理。
但是回调函数存在弊端:
- 成功/失败等函数传递太灵活了,没有规范
- 传参顺序容易错误(需要查看框架源码,以查找传参顺序)
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))
二、Promise
1. executor
new Promise 时,传入的回调函数(resolve,reject)⇒{}就是executor执行器,这个函数是是立即执行的。
new Promise((resolve, reject) => {
console.log(`executor 立即执行`)
})
调用resolve或者reject触发then传入的回调函数。
调用reject触发catch传入的回调函数。
2. 使用Promise重构requestData
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url === 'iceweb.io') {
//只能传递一个参数
resolve('我成功了,把获取到的数据传出去')
} else {
reject('url错误,请求失败')
}
}, 3000)
})
}
//1. 请求成功
requestData('iceweb.io').then(res => {
//我成功了,把获取到的数据传出去
console.log(res)
})
//2. 请求失败
//2.2 第一种写法
//url错误,请求失败
requestData('iceweb.org').then(res => {},rej => console.log(rej))
//2.2 第二种写法
//url错误,请求失败
requestData('iceweb.org').catch(e => console.log(e))
在使用上,正确的时候调用resolve方法,失败调用reject方法。
在异常处理中:
then方法可以传入两个回调 (fulfilled, rejected)=> {}
- fulfilled (resolve后执行)
- rejected(reject后执行,传入这个就可以不调用catch了)
引入Promise的好处:
- 统一规范,增强阅读性和扩展性
- 小幅度减少回调地狱
3. Promise的状态
一个承诺,其有三个状态,分别为:待定、已兑现、已拒绝。
在Promise中对应:
- pending:待定,执行了executor还在等待结果
- fulfilled:已兑现,执行了resolve会改变状态为fulfilled
- rejected:已拒绝,执行了reject会改变状态为rejected
状态一旦发生改变就不可逆转
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败')
resolve('成功')
}, 3000);
})
promise.then(res => console.log(res)).catch(err => console.log(err))
//失败
调用reject后,状态已经发生改变,不可逆,再调用resolve是无效的
4. Promise中调用resolve传入不同类型的值的区别
- **传入普通值或者对象:**这个值会作为then的回调参数
- **传入另一个Promise:**新Promise会决定原Promise的状态
- **传入有thenable对象:**会执行then方法,并传入resolve和reject,此时的状态取决于then方法内调用了resolve还是reject,这称为thenable模式。
thenable对象:含有then方法的对象then(resolve, reject)
下方个示例分别对应上方三种情况:
const promise = new Promise((resolve, reject) => {
resolve({name: 'ice', age: 22})
})
promise.then(res => console.log(res))
// {name: 'ice', age: 22}
const promise = new Promise((resolve, reject) => {
resolve(new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ice')
}, 3000);
}))
})
promise.then(res => console.log(res))
//3s后 ice
const promise = new Promise((resolve, reject) => {
resolve({
then(res, rej) {
res('hi ice')
}
})
})
promise.then(res => console.log(res))
// hi ice
5. Promise的实例方法
即存放在Promise.prototype中的方法
(1)then
- then(resolve, reject)
- 接收两个参数,第一个是成功的回调,第二个是失败的回调
- 如果只捕获错误,可以在第一个参数设置为null或者””占位
const promise = new Promise((resolve, reject) => {
// resolve('request success')
reject('request error')
})
promise.then(null, rej => console.log(rej))
//request error
- then多次调用:会重复传入then的回调函数,但是Promise内部的executor不会重复执行,会直接将结果传给回调函数(Promise的状态发生改变不可逆)
-
方法返回值:返回一个Promise,这个返回的Promise的状态如何决定?
- 返回普通值(不返回值则视为返回undefined):相当于主动调用Promise.resolve并法返回值传递到then中,状态为fulfilled
const promise = new Promise((resolve, reject) => { resolve('hi ice') }) promise.then(res => ({name:'ice', age:22})) .then(res => console.log(res)) //{name:'ice', age:22}- 主动返回一个Promise,那么这个返回对象的状态和这个返回的Promise和其内部调用的是resolve还是reject有关。
const promise = new Promise((resolve, reject) => { resolve('hi ice') }) promise.then(res => { return new Promise((resolve, reject) => { resolve('then 的返回值') }) }).then(res => console.log(res)) //then 的返回值 // 状态为fulfilled- 返回一个thenable对象:状态取决于是thenable对象内调用了resolve还是reject
promise.then(res => { return new Promise((resolve, reject) => { resolve('then 的返回值') }) })
(2)catch
- catch(err_callback)
- 多次调用和then同理
- 返回值和then同理
(3)finally方法
ES9(2018)新实例方法
不论promise最后的状态是fulfilled还是reject都会执行
const promise = new Promise((resolve, reject) => {
resolve('hi ice')
})
promise.then(res => console.log(res)).finally(() => console.log('finally execute'))
//finally execute
6. Promise的类方法/静态方法
(1)resolve:状态为fulfilled,已预知结果可以这样写
Promise.resolve('ice')
//等价于
new Promise((resolve, reject) => resolve('ice'))
(2)reject:状态为rejected,已预知结果可以这样写
Promise.reject('ice error')
//等价于
new Promise((resolve, reject) => reject('ice error'))
(3)all:传入一个可迭代对象,返回一个promise
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi ice')
}, 1000);
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi panda')
}, 2000);
})
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi grizzly')
}, 3000);
})
Promise.all([promise1, promise2, promise3]).then(res => console.log(res))
//[ 'hi ice', 'hi panda', 'hi grizzly' ]
- 当可迭代对象里面所有promise都resolve的时候,才会调用then(返回的Promise才为fulfilled),且then传入的是各个promise的结果数组
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi ice')
}, 1000);
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi panda')
}, 2000);
})
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi grizzly')
}, 3000);
})
Promise.all([promise1, promise2, promise3]).then(res => console.log(res))
//[ 'hi ice', 'hi panda', 'hi grizzly' ]
- 只要有一个promise为rejected,那么会调用catch方法(rejected),catch传入第一个reject的结果
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi ice')
}, 1000);
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('hi panda')
}, 2000);
})
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi grizzly')
}, 3000);
})
Promise.all([promise1, promise2, promise3]).then(res => console.log(res)).catch(err => console.log(err))
//hi panda
(3)allSettled
对于all方法,当存在reject时,只会在catch传入第一个reject的数据,后面其他的promise的结果会丢失。
ES11新增语法 Promise.allSettled ,无论结果是fulfilled还是rejected,都会把结果传递出来。
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('hi ice')
}, 1000);
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi panda')
}, 2000);
})
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('hi grizzly')
}, 3000);
})
Promise.allSettled([promise1, promise2, promise3]).then(res => console.log(res))
/* [
{ status: 'rejected', reason: 'hi ice' },
{ status: 'fulfilled', value: 'hi panda' },
{ status: 'rejected', reason: 'hi grizzly' }
] */
- 该方法需要所有Promise都有结果(fulfilled或rejected)以后,才会有结果。
- 其中一个没有结果,那么allSettled方法始终得不到结果。
(4)race
竞赛,将传入的promise列表中,第一个获得的结果(不管是fulfilled还是rejected)作为自己的结果。
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('hi error')
}, 1000);
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi panda')
}, 2000);
})
Promise.race([promise1, promise2])
.then(res => console.log(res))
.catch(e => console.log(e))
//hi error
(5)any
any success,或第一个fulfilled的结果作为结果,如果全都rejected,则报错 AggregateError
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('hi error')
}, 1000);
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi panda')
}, 2000);
})
Promise.any([promise1, promise2])
.then(res => console.log(res))
.catch(e => console.log(e))
//hi panda
三、生成器+Promise解决回调地狱
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes('iceweb')) {
resolve(url)
} else {
reject('请求错误')
}
}, 1000);
})
}
function* getData(url) {
const res1 = yield requestData(url)
const res2 = yield requestData(res1)
const res3 = yield requestData(res2)
console.log(res3)
}
const generator = getData('iceweb.io')
generator.next().value.then(res1 => {
generator.next(`iceweb.org ${res1}`).value.then(res2 => {
generator.next(`iceweb.com ${res2}`).value.then(res3 => {
generator.next(res3)
})
})
})
//iceweb.com iceweb.org iceweb.io
再进行异步自动化封装,解决上方的回调地狱
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes('iceweb')) {
resolve(url)
} else {
reject('请求错误')
}
}, 1000);
})
}
function* getData() {
const res1 = yield requestData('iceweb.io')
const res2 = yield requestData(`iceweb.org ${res1}`)
const res3 = yield requestData(`iceweb.com ${res2}`)
console.log(res3)
}
//自动化执行 async await相当于自动帮我们执行.next
function asyncAutomation(genFn) {
const generator = genFn()
const _automation = (result) => {
let nextData = generator.next(result)
if(nextData.done) return
nextData.value.then(res => {
_automation(res)
})
}
_automation()
}
asyncAutomation(getData)
//iceweb.com iceweb.org iceweb.io
回调地狱最终解决办法
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes('iceweb')) {
resolve(url)
} else {
reject('请求错误')
}
}, 1000);
})
}
async function getData() {
const res1 = await requestData('iceweb.io')
const res2 = await requestData(`iceweb.org ${res1}`)
const res3 = await requestData(`iceweb.com ${res2}`)
console.log(res3)
}
getData()
//iceweb.com iceweb.org iceweb.io
我们发现, 把getData生成器函数改成async函数,yield关键字替换为await,就和上方的生成器代码类似。async await的核心代码就是和上方的 generatror+promise类似。
四、async/await
1. async异步函数
async关键字用于定义一个异步函数
await用于控制函数的执行,只有当await后面的 thenable对象的状态变为resolved或者rejected(需要捕获异常,否则会报错)后才会继续执行。
async function sayHi() {
console.log('hi ice')
}
sayHi()
//hi ice
异步函数的返回值
- 返回值类型确定:是一个Promise
- 如果函数内返回的值是一个普通的值,那么返回的Promise相当于
Promise.resolve的返回值 - 如果函数内返回的值是一个thenable对象,那么返回的Promise的状态与then中调用的resolve或者reject有关。
- 如果明确返回一个Promise,那么返回的Promise的状态由明确返回的Promise决定。
异步函数的异常处理
- 如果函数内部发生错误,可以在调用处用try catch捕获
- 如果函数内部发射管错误,也可用函数的返回值.catch进行捕获(本质上返回值是Promise)
async function sayHi() {
console.log(res)
}
sayHi().catch(e => console.log(e))
//或者
async function sayHi() {
try {
console.log(res)
}catch(e) {
console.log(e)
}
}
sayHi()
//ReferenceError: res is not defined
2. await关键字
只有异步函数中才能用await关键字
await关键字特点
- await通常后面跟的是Promise对象。
- await后面可以跟普通值、thenable、Promise
- 只有后面的状态变为fulfilled才会执行下方的代码,否则会等待后方的代码执行完才能向下执行
- 如果await后的代码状态变为rejected,那么需要在对await代码进行
try catch捕获,或者进行.catch操作