前语
今天给我们共享promise,笔者将从前期的异步代码的困境、promise出现处理了什么问题、异步回调阴间的终极方案并且完结async await的中心语法,其实async/await仅仅generator+promise的一个变种算了。这是坚持写博客的第三周,坚持下去作业总是会变好!
1. 前期异步代码困境
- 众所周知,js是单线程的,耗时操作都是交给浏览器来处理,等时间到了从队伍中取出实行,规划到事情循环的概念,笔者也共享过,可以看以下,了解了可以更好的了解
promise
。 - 我以一个需求为切入点,我模仿网络央求(异步操作)
- 假设网络央求成功了,你奉告我成功了
- 假设网络央求失利了,你奉告我失利了
1.1 大聪明做法
function requestData(url) {
setTimeout(() => {
if (url === 'iceweb.io') {
return '央求成功'
}
return '央求失利'
}, 3000)
}
const result = requestData('iceweb.io')
console.log(result) //undefined
- 首要你要了解
js
代码的实行次第,而不是是想当然的,代码其实并不是依照你书写的次第实行的。 - 那么为什么是
undefined呢
?- 首要当我实行
requestData
函数,初步实行函数。遇到了异步操作不会阻塞后边代码实行的,因为js是单线程的,所以你写的return
成功或许失利并没有回来,那我这个函数中,抛开异步操作,里面并没有回来值,所以值为undefined
。
- 首要当我实行
2.2 前期正确做法
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中函数是一等公民,可以传来传去,但是这样太灵活了,没有规范。
- 假设运用的是结构,还要阅读一下结构源码,正确失利的传实参的次第,假设传参次第差错这样是十分风险的。
2. Promise
-
Promise
(许诺),给予调用者一个许诺,过一会回来数据给你,就可以创立一个promise方针 - 当我们
new
一个promise
,此时我们需求传递一个回调函数,这个函数为当即实行的,称之为(executor) - 这个回调函数,我们需求传入两个参数回调函数,
reslove
,reject
(函数可以进行传参)- 当实行了
reslove
函数,会回调promise方针的.then函数 - 当实行了
reject
函数,会回调promise方针的.catche函数
- 当实行了
2.1 Executor当即实行
new Promise((resolve, reject) => {
console.log(`executor 当即实行`)
})
- 传入的
executor
是当即实行的
2.2 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))
- 在函数中,new这个类的时分,传入的回调函数称之为
executor
(会被Promise类中主动实行) - 在正确的时分调用
resolve
函数,失利的时分调用reject
函数,把需求的参数传递出去。 - 失常处理
- 其间在
.then
方法中可以传入两个回调,您也可以查看Promise/A+规范- 第一个则是
fulfilled
的回调 - 第二个则是
rejected
的回调
- 第一个则是
- 其间在
- 那这样有什么好处呢? 看起来比前期处理的方案还要繁琐呢?
- 统一规范,可以增强阅读性和扩展性
- 小幅度减少回调阴间
2.3 promise的情况
- 首要先给我们举个栗子,把代码抽象为实际的栗子
- 你答应你女朋友,下周末带她去吃好吃的 (还未到下周末,此时情况为待定情况)
- 时间飞快,今天便是周结尾,你和你女友一同吃了烤肉、甜点、奶茶…(已完结情况)
- 时间飞快,今天便是周结尾,正打算出门。不巧产品司理,因为线上出现的急迫问题,需求回公司处理一下,你(为了日子)只能含蓄的拒绝一下女友,并且说明一下缘由(已拒绝情况)
- 运用
promise
的时分,给它一个许诺,我们可以将他划分为三个阶段- pending(待定),实行了executor,情况还在等待中,没有被完结,也没有被拒绝
- fulfilled(已完结),实行了
resolve
函数则代表了已完结情况 - rejected(已拒绝),实行了
reject
函数则代表了已拒绝情况
- 首要,情况只需从待定情况,变为其他情况,则情况不能再改动
思考以下代码:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失利')
resolve('成功')
}, 3000);
})
promise.then(res => console.log(res)).catch(err => console.log(err))
//失利
- 当我调用
reject
之后,在调用resolve
是无效的,因为情况现已产生改动,并且是不行逆的。
2.4 resolve不同值的差异
- 假设
resolve
传入一个一般的值或许方针,只能传递接受一个参数,那么这个值会作为then
回调的参数
const promise = new Promise((resolve, reject) => {
resolve({name: 'ice', age: 22})
})
promise.then(res => console.log(res))
// {name: 'ice', age: 22}
- 假设
resolve
中传入的是别的一个Promise
,那么这个新Promise
会抉择原Promise
的情况
const promise = new Promise((resolve, reject) => {
resolve(new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ice')
}, 3000);
}))
})
promise.then(res => console.log(res))
//3s后 ice
- 假设
resolve
中传入的是一个方针,并且这个方针有完结then
方法,那么会实行该then
方法,then
方法会传入resolve
,reject
函数。此时的promise
情况取决于你调用了resolve
,仍是reject
函数。这种形式也称之为: thenable
const promise = new Promise((resolve, reject) => {
resolve({
then(res, rej) {
res('hi ice')
}
})
})
promise.then(res => console.log(res))
// hi ice
2.5 Promise的实例方法
- 实例方法,存放在
Promise.prototype
上的方法,也便是Promise的闪现原型上,当我new Promise的时分,会把回来的改方针的 promise[[prototype]](隐式原型) === Promise.prototype (闪现原型) - 即new回来的方针的隐式原型指向了Promise的闪现原型
2.5.1 then方法
2.5.1.1 then的参数
-
then
方法可以接受参数,一个参数为成功的回调,另一个参数为失利的回调,前面重构requestData
中有演练过。
const promise = new Promise((resolve, reject) => {
resolve('request success')
// reject('request error')
})
promise.then(res => console.log(res), rej => console.log(rej))
//request success
- 假设只捕获差错,还可以这样写
- 因为第二个参数是捕获失常的,第一个可以写个
null
或""
占位
- 因为第二个参数是捕获失常的,第一个可以写个
const promise = new Promise((resolve, reject) => {
// resolve('request success')
reject('request error')
})
promise.then(null, rej => console.log(rej))
//request error
2.5.1.2 then的屡次调用
const promise = new Promise((resolve, reject) => {
resolve('hi ice')
})
promise.then(res => console.log(res))
promise.then(res => console.log(res))
promise.then(res => console.log(res))
- 调用屡次则会实行屡次
2.5.1.3 then的回来值
-
then
方法是有回来值的,它的回来值是promise
,但是是promise
那它的情况怎样抉择呢?接下来让我们一探毕竟。
2.5.1.3.1 回来一个一般值 情况: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.resolve
,并且把回来值作为实参传递到then
方法中。 - 假设没有回来值,则相当于回来
undefined
2.5.1.3.2 明晰回来一个promise 情况:fulfilled
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 的回来值
- 主动回来一个
promise
方针,情况和你调用resolve
,仍是reject
有关
2.5.1.3.3 回来一个thenable方针 情况:fulfilled
const promise = new Promise((resolve, reject) => {
resolve('hi ice')
})
promise.then(res => {
return {
then(resolve, reject) {
resolve('hi webice')
}
}
}).then(res => console.log(res))
//hi webice
- 回来了一个thenable方针,其情况取决于你是调用了
resolve
,仍是reject
2.5.2 catch方法
2.5.2.1 catch的屡次调用
const promise = new Promise((resolve, reject) => {
reject('ice error')
})
promise.catch(err => console.log(err))
promise.catch(err => console.log(err))
promise.catch(err => console.log(err))
//ice error
//ice error
//ice error
2.5.2.2 catch的回来值
- catch方法是有回来值的,它的回来值是promise,但是是promise那它的情况怎样抉择呢?接下来让我们一探毕竟。
- 假设回来值明晰一个promise或许thenble方针,取决于你调用了
resolve
仍是reject
2.5.2.2.1 回来一个一般方针
const promise = new Promise((resolve, reject) => {
reject('ice error')
})
promise.catch(err => ({name:'ice', age: 22})).then(res => console.log(res))
//{name:'ice', age: 22}
2.5.2.2.2 明晰回来一个promise
const promise = new Promise((resolve, reject) => {
reject('ice error')
})
promise.catch(err => {
return new Promise((resolve, reject) => {
reject('ice error promise')
})
}).catch(res => console.log(res))
//ice error promise
- 此时
new Promise()
调用了reject
函数,则会被catch
捕获到
2.5.2.2.3 回来thenble方针
const promise = new Promise((resolve, reject) => {
reject('ice error')
})
promise.catch(err => {
return {
then(resolve, reject) {
reject('ice error then')
}
}
}).catch(res => console.log(res))
//ice error then
2.5.3 finally方法
- ES9(2018)新实例方法
- finally(最终),不论promise情况是fulfilled仍是rejected都会实行一次
finally
方法
const promise = new Promise((resolve, reject) => {
resolve('hi ice')
})
promise.then(res => console.log(res)).finally(() => console.log('finally execute'))
//finally execute
2.6 Promise中的类方法/静态方法
2.6.1 Promise.reslove
Promise.resolve('ice')
//等价于
new Promise((resolve, reject) => resolve('ice'))
- 有的时分,你现已预知了情况的效果为fulfilled,则可以用这种简写方法
2.6.2 Promise.reject
Promise.reject('ice error')
//等价于
new Promise((resolve, reject) => reject('ice error'))
- 有的时分,你现已预知了情况的效果为rejected,则可以用这种简写方法
2.6.3 Promise.all
fulfilled 情况
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' ]
- all方法的参数传入为一个可迭代方针,回来一个promise,只需三个都为
resolve
情况的时分才会调用.then
方法。 - 只需有一个promise的情况为rejected,则会回调
.catch
方法
rejected情况
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
- 当遇到rejectd的时分,后续的promise效果我们是获取不到,并且会把reject的实参,传递给catch的err形参中
2.6.4 Promise.allSettled
- 上面的
Promise.all
有一个缺陷,便是当遇到一个rejected的情况,那么对于后边是resolve
或许reject
的效果我们是拿不到的 - ES11 新增语法
Promise.allSettled
,不论情况是fulfilled/rejected都会把参数回来给我们
所有promise都有效果
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,才会有毕竟的效果
其间一个promise没有效果
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) => {})
Promise.allSettled([promise1, promise2, promise3]).then(res => console.log(res))
// 什么都不打印
- 其间一个promise没有效果,则什么都效果都拿不到
2.6.5 Promise.race
- race(竞赛竞赛)
- 优先获取第一个回来的效果,不论效果是fulfilled仍是rejectd
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
2.6.6 Promise.any
- 与race类似,只获取第一个情况为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
3. Promise的回调阴间 (进阶)
- 我仍是以一个需求作为切入点,把知识点嚼碎了,一点一点喂进你们嘴里。
- 当我发送网络央求的时分,需求拿到这次网络央求的数据,再发送网络央求,就这样重复三次,才能拿到我毕竟的效果。
3.1 卧龙解法
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes('iceweb')) {
resolve(url)
} else {
reject('央求差错')
}
}, 1000);
})
}
requestData('iceweb.io').then(res => {
requestData(`iceweb.org ${res}`).then(res => {
requestData(`iceweb.com ${res}`).then(res => {
console.log(res)
})
})
})
//iceweb.com iceweb.org iceweb.io
- 尽管可以完结,但是多层代码的嵌套,可读性十分差,我们把这种多层次代码嵌套称之为回调阴间
3.2 凤雏解法
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes('iceweb')) {
resolve(url)
} else {
reject('央求差错')
}
}, 1000);
})
}
requestData('iceweb.io').then(res => {
return requestData(`iceweb.org ${res}`)
}).then(res => {
return requestData(`iceweb.com ${res}`)
}).then(res => {
console.log(res)
})
//iceweb.com iceweb.org iceweb.io
- 运用了then链式调用这一特性,回来了一个新的promise,但是不行高雅,思考一下能不能写成同步的方法呢?
3.3 生成器+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
- 我们可以发现我们的
getData
现已变为同步的方法,可以拿到我毕竟的效果了。那么许多同学会问,generator一向调用.next
不是也产生了回调阴间吗? - 其实不用关心这个,我们可以发现它这个是有规律的,我们可以封装成一个主动化实行的函数,我们就不用关心内部是怎样调用的了。
3.4 主动化实行函数封装
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()
}
syncAutomation(getData)
//iceweb.com iceweb.org iceweb.io
- 运用promise+生成器的方法变相完结处理回调阴间问题,其实便是
async await
的一个变种算了 - 最早为TJ完结,前端大神人物
- async await中心代码就类似这些,内部主动帮我们调用
.next
方法
3.5 毕竟处理回调阴间的方法
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
函数,yeild
的关键字替换为await
就可以完结异步代码同步写法了。
4. async/await 剖析
- async(异步的)
- async 用于声明一个异步函数
4.1 async内部代码同步实行
- 异步函数的内部代码实行过程和一般的函数是一致的,默许情况下也是会被同步实行
async function sayHi() {
console.log('hi ice')
}
sayHi()
//hi ice
4.2 异步函数的回来值
-
异步函数的回来值和一般回来值有所差异
- 一般函数主动回来什么就回来什么,不回来为
undefined
- 异步函数的回来值特征
- 明晰有回来一个一般值,相当于
Promise.resolve
(回来值) - 回来一个thenble方针则由,then方法中的
resolve
,或许reject
有关 - 明晰回来一个promise,则由这个promise抉择
- 明晰有回来一个一般值,相当于
- 一般函数主动回来什么就回来什么,不回来为
-
异步函数中可以运用
await
关键字,现在在大局也可以进行await
,但是不推荐。会阻塞主进程的代码实行
4.3 异步函数的失常处理
- 假设函数内部半途产生差错,可以经过try catch的方法捕获失常
- 假设函数内部半途产生差错,也可以经过函数的回来值.catch进行捕获
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
4.4 await 关键字
- 异步函数中可以运用
await
关键字,一般函数不行 - await特征
- 一般await关键字后边都是跟一个Promise
- 可以是一般值
- 可以是thenble
- 可以是Promise主动调用
resolve或许reject
- 这个promise情况变为fulfilled才会实行
await
后续的代码,所以await
后边的代码,相当于包含在.then
方法的回调中,假设情况变为rejected,你则需求在函数内部try catch
,或许进行链式调用进行.catch
操作
- 一般await关键字后边都是跟一个Promise
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes('iceweb')) {
resolve(url)
} else {
reject('央求差错')
}
}, 1000);
})
}
async function getData() {
const res = await requestData('iceweb.io')
console.log(res)
}
getData()
// iceweb.io
5. 结语
- 假设现在真的看不到未来是怎样,你就不如一向往前走,不知道什么时分天亮,去奔跑就好,跑着跑着天就亮了。