今天給大家分享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 => ({name:'ice', age:22})) .then(res => console.log(res)) //{name:'ice', age:22}
- 调用多次则会执行多次
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() } asyncAutomation(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. 结语
- 如果现在真的看不到未来是怎样,你就不如一直往前走,不知道什么时候天亮,去奔跑就好,跑着跑着天就亮了。
【相关推荐:javascript视频教程、编程基础视频】

async是es7的。async和await是ES7中新增内容,是对于异步操作的解决方案;async/await可以说是co模块和生成器函数的语法糖,用更加清晰的语义解决js异步代码。async顾名思义是“异步”的意思,async用于声明一个函数是异步的;async和await有一个严格规定,两者都离不开对方,且await只能写在async函数中。

去掉重复并排序的方法:1、使用“Array.from(new Set(arr))”或者“[…new Set(arr)]”语句,去掉数组中的重复元素,返回去重后的新数组;2、利用sort()对去重数组进行排序,语法“去重数组.sort()”。

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于Symbol类型、隐藏属性及全局注册表的相关问题,包括了Symbol类型的描述、Symbol不会隐式转字符串等问题,下面一起来看一下,希望对大家有帮助。

怎么制作文字轮播与图片轮播?大家第一想到的是不是利用js,其实利用纯CSS也能实现文字轮播与图片轮播,下面来看看实现方法,希望对大家有所帮助!

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于对象的构造函数和new操作符,构造函数是所有对象的成员方法中,最早被调用的那个,下面一起来看一下吧,希望对大家有帮助。

await是es7。async和await是ES7中新增内容,是对于异步操作的解决方案。async顾名思义是“异步”的意思,async用于声明一个函数是异步的;而await从字面意思上是“等待”的意思,就是用于等待异步完成。async和await有一个严格规定,两者都离不开对方,且await只能写在async函数中。

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于面向对象的相关问题,包括了属性描述符、数据描述符、存取描述符等等内容,下面一起来看一下,希望对大家有帮助。

方法:1、利用“点击元素对象.unbind("click");”方法,该方法可以移除被选元素的事件处理程序;2、利用“点击元素对象.off("click");”方法,该方法可以移除通过on()方法添加的事件处理程序。

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

Dreamweaver CS6
視覺化網頁開發工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

記事本++7.3.1
好用且免費的程式碼編輯器

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中