摘要
Promise 對象用于清晰的處理異步任務的完成,返回最終的結果值,本次分享主要介紹 Promise 的基本屬性以及 Promise 內部的基礎實現,能夠幫我們更明確使用場景、更快速定位問題。Promise 出現的原因
首先我們先來看一段代碼:異步請求的層層嵌套
function fn1(params) {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
const fn1Data = {name: 'fn1'}
console.log(fn1Data, 'fn1Data');
// 請求2
(function fn2() {
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
const fn2Data = {name: `${fn1Data.name}-fn2`}
console.log(fn2Data, 'fn2Data');
// 請求3
(function fn2() {
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
const fn3Data = {name: `${fn2Data.name}-fn3`}
console.log(fn3Data, 'fn3Data');
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
})()
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
})()
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
}
fn1()
或者我們可以將上面的代碼優化為下面這樣
function fn1(params) {
console.log(`我是fn1,我在函數${params}中執行!!!`);
}
function fn2(params) {
try {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log(`我是fn2,我在函數${params}中執行!!!結果是:`,params.data);
fn1('fn2')
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
} catch (error) {
console.error(error);
}
}
function fn3() {
try {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log('fn3請求已完成');
fn2('fn3')
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
console.log('我是f3函數呀');
} catch (error) {
console.error(error);
}
}
fn3()
由上面的兩種寫法的請求可見,在 promise 之前,為了進行多個異步請求并且依賴上一個異步請求的結果時,我們必須進行層層嵌套,大多數情況下,我們又對異步結果進行數據處理,這樣使得我們的代碼非常難看,并且難以維護,這就形成了回調地獄,由此 Promise 開始出現了。回調地獄缺點-
代碼臃腫
-
可讀性差
-
耦合性高
-
不好進行異常處理
Promise 的基本概念
含義
-
ES6 將其寫進了語言標準里統一了用法,是一個構造函數,用來生成 Promise 實例
-
參數為一個執行器函數 (執行器函數是立即執行的), 該函數有兩個函數作為參數,第一個參數是成功時的回調,第二個參數是失敗時的回調
-
函數的方法有 resolve (可以處理成功和失敗)、reject (只處理失敗)、all 等方法
-
then、catch、finally 方法為 Promise 實例上的方法
狀態
-
pending --- 等待狀態
-
Fulfilled --- 執行狀態 (resolve 回調函數,then)
-
Rejected --- 拒絕狀態 (reject 回調函數,catch)
-
狀態一旦改變就不會再變,狀態只可能是兩種改變,從 pending->Fulfilled,pending->Rejected
-
有兩個關鍵的屬性:PromiseState --- 狀態改變,PromiseResult --- 結果數據改變
const p1 = Promise.resolve(64)
const p2 = Promise.reject('我錯了')
const p3 = Promise.then()
const p4 = Promise.catch()
// 狀態改變PromiseState 結果改變PromiseResult
console.log(new Promise(()=>{}), 'Promise'); // PromiseState='pending' PromiseResult=undefined
console.log(p1,'p1'); // PromiseState='Fulfilled' PromiseResult=64
console.log(p2,'p2'); // PromiseState="Rejected" PromiseResult='我錯了'
console.log(p3, 'p3'); // then為實例上的方法,報錯
console.log(p4, 'p4'); // catch為實例上的方法,報錯
?特點1.錯誤信息清晰定位:可以在外層捕獲異常信息(網絡錯誤、語法錯誤都可以捕獲),有 “冒泡” 性質,會一直向后傳遞,直到被捕獲,所以在最后寫一個 catch 就可以了2.鏈式調用:每一個 then 和 catch 都會返回一個新的 Promise,把結果傳遞到下一個 then/catch 中,因此可以進行鏈式調用 --- 代碼簡潔清晰結果由什么決定
resolve
-
如果傳遞的參數是非 Promise 類型的對象,則返回的結果是成功狀態的 Promise 對象,進入下一個 then 里面
-
如果傳遞的參數是 Promise 類型的對象,則返回的結果由返回的 Promise 決定,如果返回的是 resolve 則是成功的狀態,進入下一個 then 里,如果返回的是 reject 則是失敗的狀態,進入下一個 catch 里
reject
-
如果傳遞的參數是非 Promise 類型的對象,則返回的結果是拒絕狀態的 Promise 對象,進入下一個 catch 里面或者是下一個 then 的第二個參數 reject 回調里面
-
如果傳遞的參數是 Promise 類型的對象,則返回的結果由返回的 Promise 決定,如果返回的是 resolve 則是成功的狀態,進入下一個 then 里,如果返回的是 reject 則是拒絕的狀態,進入下一個 catch 里面或者是下一個 then 的第二個參數 reject 回調里面
流程圖
?簡單使用
// 模擬一個promise的get請求
let count = 0
function customGet(url){
count += 1
return new Promise((resolve, reject)=>{
const xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET",url, true);
xmlHttp.onload = ()=>{
console.log(xmlHttp, 'xmlHttp---onload');
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log('customGet請求成功了');
// 返回非Promise,結果為成功狀態
resolve({data:`第${count}次請求獲取數據成功`})
// 返回Promise,結果由Promise決定
// resolve(Promise.reject('resolve中返回reject'))
} else {
reject('customGet請求錯誤了')
}
}
// Promise狀態改變就不會再變
// onreadystatechange方法會被執行四次
// 當地次進來的時候,readyState不等于4,執行else邏輯,執行reject,狀態變為Rejected,所以即使再執行if,狀態之后不會再改變
// xmlHttp.onreadystatechange = function(){
// console.log(xmlHttp,'xmlHttp---onreadystatechange')
// if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
// console.log('customGet請求成功了');
// resolve({data:`第${count}次請求獲取數據成功`})
// } else {
// reject('customGet請求錯誤了')
// }
// }
xmlHttp.send();
})
}
// 使用Promise,并且進行鏈式調用
customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=').then((res)=>{
console.log(res.data);
return '第一次請求處理后的數據'
}).then((data)=>{
console.log(data)
// console.log(data.toFixed());
return customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=')
}).then((res)=>{
console.log(res.data);
}).catch((err)=>{
// 以類似'冒泡'的性質再外層捕獲所有的錯誤
console.error(err, '這是catch里的錯誤信息');
})
手寫實現簡單的 Promise通過上面的回顧,我們已經了解了 Promise 的關鍵屬性和特點,下面我們一起來實現一個簡單的 Promise 吧
// 1、封裝一個Promise構造函數,有一個函數參數
function Promise(executor){
// 7、添加對象屬性PromiseState PromiseResult
this.PromiseState = 'pending'
this.PromiseResult = null
// 14、創建一個保存成功失敗回調函數的屬性
this.callback = null
// 8、this指向問題
const that = this
// 4、executor有兩個函數參數(resolve,reject)
function resolve(data){
// 10、Promise狀態只能修改一次(同時記得處理reject中的狀態)
if(that.PromiseState !== 'pending') return
// console.log(this, 'this');
// 5、修改對象的狀態PromiseState
that.PromiseState = 'Fulfilled'
// 6、修改對象的結果PromiseResult
that.PromiseResult = data
// 15、異步執行then里的回調函數
if(that.callback?.onResolve){
that.callback.onResolve(that.PromiseResult)
}
}
function reject(data){
console.log(that.PromiseState, 'that.PromiseState');
if(that.PromiseState !== 'pending') return
// 9、處理失敗函數狀態
that.PromiseState = 'Rejected'
that.PromiseResult = data
console.log(that.PromiseResult, 'that.PromiseResult');
console.log(that.PromiseState, 'that.PromiseState');
// 16、異步執行then里的回調函數
if(that.callback?.onReject){
that.callback.onReject(that.PromiseResult)
}
}
// 3、執行器函數是同步調用的,并且有兩個函數參數
executor(resolve,reject)
}
// 2、函數的實例上有方法then
Promise.prototype.then = function(onResolve,onReject){
// 20、處理onReject沒有的情況
if(typeof onReject !== 'function'){
onReject = reason => {
throw reason
}
}
// 21、處理onResolve沒有的情況
if(typeof onResolve !== 'function'){
onResolve = value => value
}
// 17、每一個then方法都返回一個新的Promise,并且把上一個then返回的結果傳遞出去
return new Promise((nextResolve,nextReject)=>{
// 11、處理成功或失敗
if(this.PromiseState === 'Fulfilled'){
// 12、將結果傳遞給函數
// onResolve(this.PromiseResult)
// 18、拿到上一次執行完后返回的結果,判斷是不是Promise
const result = onResolve(this.PromiseResult)
if(result instanceof Promise){
result.then((v)=>{
nextResolve(v)
},(r)=>{
nextReject(r)
})
} else {
nextResolve(result)
}
}
// 當你一步步寫下來的時候有沒有懷疑過為什么不用else
if(this.PromiseState === 'Rejected'){
// 第12步同時處理此邏輯
// onReject(this.PromiseResult)
// 22、處理catch異常穿透捕獲錯誤
try {
const result = onReject(this.PromiseResult)
if(result instanceof Promise){
result.then((v)=>{
nextResolve(v)
}).catch((r)=>{
nextReject(r)
})
} else {
nextReject(result)
}
} catch (error) {
nextReject(this.PromiseResult)
}
}
// 13、異步任務時處理成功或失敗,想辦法等異步任務執行完成后才去執行這兩個函數
if(this.PromiseState === 'pending'){
this.callback = {
onResolve,
onReject
}
console.log(this.callback, 'this.callback');
}
})
}
// 19、函數實例上有方法catch
Promise.prototype.catch = function(onReject) {
return this.then(null,onReject)
}
// 使用自定義封裝的Promise
const customP = new Promise((resolve,reject)=>{
// 模擬異步執行請求
// const xmlHttp = new XMLHttpRequest();
// xmlHttp.open("GET",'https://v0.yiketianqi.com/api/cityall?appid=&appsecret=', true);
// xmlHttp.onload = ()=>{
// if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
// resolve('success')
// } else {
// reject('error')
// }
// }
// xmlHttp.send();
// 同步執行
resolve('success')
// reject('error')
})
console.log(customP, 'customP');
customP.then((res)=>{
console.log(res, 'resolve回調');
return '第一次回調'
// return new Promise((resolve,reject)=>{
// reject('錯錯錯')
// })
},(err)=>{
console.error(err, 'reject回調');
return '2121'
}).then(()=>{
console.log('then里面輸出');
}).then().catch((err)=>{
console.error(err, 'catch里的錯誤');
})
針對 resolve 中返回 Promise 對象時的內部執行順序??總結以上就是我們常用的 Promise 基礎實現,在實現過程中對比了 Promise 和函數嵌套處理異步請求的優缺點,Promise 仍存在缺點,但是的確方便很多,同時更清晰的理解到錯誤處理如何進行異常穿透的,也能幫助我們更規范的使用 Promise 以及快速定位問題所在。
-
耦合
+關注
關注
13文章
583瀏覽量
100905 -
代碼
+關注
關注
30文章
4801瀏覽量
68735 -
執行器
+關注
關注
5文章
378瀏覽量
19373
原文標題:Promise規范與原理解析
文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論