JS——非同步程式設計的六種解決方案
將需要在非同步任務後執行的操作,作為引數傳入到非同步操作中,當非同步操作執行完成後,呼叫該引數執行後面的操作
ajax(url,()=>{})
回撥函式簡單,容易理解和實現;但回撥函式的缺點就是,容易寫出回撥地獄
多個非同步操作需要規定執行順序時產生回撥地獄
回撥地獄導致程式碼不容易閱讀和維護,各個部分高度耦合,使得程式結構混亂,流程那一追蹤
ajax(url,()=>{ //處理邏輯 ajax(url1,()=>{ //處理邏輯 aja(url2,()=>{ //處理邏輯 }) }) })
-
事件監聽
非同步任務的執行不取決於程式碼的順序,而是取決於某個事件是否發生,事件驅動
f1.on('done',f2) //f1 繫結事件 done,只有當f1發生done事件的時候才執行f2 function f1(){ setTimeout(function(){ //…… f1.trigger('done') //觸發done事件,f2立即執行 }, 1000) }
這種方式容易理解,可以繫結多個事件,每個事件可以指定多個回撥函式,而且可以“去耦合”,有利於實現模組化。缺點就是整個程式都會變成事件驅動型,執行流程變得很不清晰
-
釋出訂閱
假定存在一個"訊號中心",某個任務執行完成,就想訊號中心"釋出(publish)"一個訊號,其他任務可以向訊號中心"訂閱(subscribe)"這個訊號,從而知道什麼時候自己可以執行。這就是釋出訂閱模式,又稱"觀察者模式"
jQuery.subscribe('done',f2) //f2 向訂閱中心訂閱了 done 訊號 function f1(){ setTimeout(function(){ //…… jQuery.publish('done') //執行完成後向訂閱中心釋出done訊號 },1000) } jQuery.unsubscribe('done',f2) //f2執行完成後可以取消訂閱
這種方式與"事件監聽類似",但明顯後者優於前者。因為可以通過檢視"訊息中心",瞭解存在多少訊號,每個訊號有多少訂閱者,從而監聽程式的執行
-
Promise
ES6引入的非同步程式設計解決方案
/*##回撥函式非同步解決方案*/ setTimeout(()=>{ console.log('hello world'); setTimeout(()=>{ console.log('hello vueJs'); setTimeout(()=>{ console.log('hello Promise'); //回撥函式產生的回撥地獄 },1000) },1000) }, 1000) /*##Promise非同步解決方案*/ new Promise((resolve, reject)=>{ //第一次非同步請求 setTimeout(()=>{ //成功的時候呼叫resolve resolve('hello world') //通過resolve函式將結果返回到外部 then 處理 }, 1000) }).then((data)=>{ //處理第一次非同步請求 console.log(data); //第二次非同步請求 return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve() },1000) }) }).then(()=>{ //處理第二次非同步請求 console.log('hello vueJs'); //第三次請求 return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve() },1000) }) }).then(()=>{ //處理第三次非同步請求 console.log('hello Promise'); })
-
Promise 怎麼使用
-
將非同步操作放進 Promise 中
-
new Promise(回撥函式)
new -> 建構函式(1.儲存一些狀態資訊,2.執行傳入的引數(即回撥函式))
-
回撥函式:(resolve,reject)=>{非同步操作}
-
resolve 和 reject 本身又是函式
-
通過 resolve 和 reject 將資料返回到外部 .then() .catch處理 --> 鏈式程式設計
可以正確和錯誤的回撥都在 then() 中處理
new Promise((resolve,reject)=>{ setTimeout(()=>{ //遇到錯誤呼叫reject,將錯誤返回到外部 catch 處理 reject('hello error') },1000) }).then(data=>{ console.log(data); },err=>{ console.log(err); })
-
-
Promise 的三種狀態
pending
: 等待狀態,比如正在進行的網路請求,或定時器沒有到時間
fulfill
: 滿足狀態,主動回撥 resolve 時,就處於滿足狀態,並且回撥 .then()
reject
: 拒絕狀態,主動回撥 reject 時,就處於拒絕狀態,並且回撥 .catch() -
Promise的鏈式呼叫
graph LR a(返回新的Promise物件)-->b(返回Promise.resolve) b-->c(直接返回資料)/*最開始的鏈式呼叫*/ new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve('hello1') }) }).then((data)=>{ console.log(data,"第一層資料處理"); return new Promise(resolve => { resolve(data+'1') }) }).then(data=>{ console.log(data,'第二層資料處理'); return new Promise(resolve => { resolve(data+'1') }) }).then(data=>{ console.log(data,'第三層資料處理') }) /*簡化new的鏈式呼叫*/ new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve('hello1') }) }).then((data)=>{ console.log(data,"第一層資料處理"); // return Promise.resolve(data+'1') return Promise.reject('error') //丟擲錯誤資訊的簡化方式 }).then(data=>{ console.log(data,'第二層資料處理'); return Promise.resolve(data+'1') }).then(data=>{ console.log(data,'第三層資料處理') }).catch(err=>{ console.log(err); }) /*最終簡化版*/ new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve('hello1') }) }).then((data)=>{ console.log(data,"第一層資料處理"); // return data+'1' throw 'error' //丟擲錯誤的簡寫方式 }).then(data=>{ console.log(data,'第二層資料處理'); return data+'1' }).then(data=>{ console.log(data,'第三層資料處理') }).catch(err=>{ console.log(err); })
-
Promise.all
當一個任務需要多個非同步任務的結果才能執行時
/*1. 通過回撥函式實現*/ let isResult1 = false let isResult2 = false $ajax({ url: '', success: function () { console.log('結果1'); isResult1 = true handleResult() } }) $ajax({ url: '', success: function () { console.log('結果2'); isResult2 = true handleResult() } }) function handleResult() { if(isResult1 && isResult2){ //邏輯處理 } } /*2. Promise的all方法,同時進行多個非同步操作*/ Promise.all([ new Promise((resolve,reject)=>{ $ajax({ url: 'url1', success: function (data) { resolve(data) } }) }), new Promise((resolve,reject)=>{ $ajax({ url: 'url2', success: function (data) { resolve(data) } }) }), ]).then(results=>{ results[0] //第一個非同步操作返回的結果 results[1] //第二個非同步操作返回的結果 })
-
-
生成器 Generator/yield
Generator與函式很像,但定義時多個 * 號,並且除了 return 以外,還可以用 yield 返回多次
function* foo(x){ yield x+1 yield x+2 return x+3 } let f = foo(3); f.next() //{value: 4, done: false} f.next() //{value: 5, done: false} f.next() //{value: 6, done: true} f.next() //{value: undefined, done: true} //也可以直接用迴圈地跌 generator 物件 for(var x fo foo(3)){ console.log(x) }
generator 函式的特性很適合用來處理非同步操作因為它的每一步yield都是按順序的
function *fetch() { yield ajax(url, () => {}) yield ajax(url1, () => {}) yield ajax(url2, () => {}) } let it = fetch() let result1 = it.next() let result2 = it.next() let result3 = it.next()
-
Aysnc/Await
Aysnc 函式是 Generator 的優化
Aysnc 是基於 Promise 實現的,Aysnc 返回一個 Promise 物件
-
Aysnc 對 Generator 的改進
-
內建執行器
Generator 函式執行必須依靠執行器,所以才有了 co 函式庫,而 aysnc 函式只帶執行器。也就是說 async 函式的執行與普通函式的執行一模一樣,只要一行
-
更廣適用性
yield 命令後面只能是 Thunk 函式或者 Promise 物件,而 async 函式的 await 命令後面,可以跟 Promise 物件和 原始型別的值(數值,字串和布林值,但這時等同於同步操作)
-
更好的語義
async 和 await,比起 * 號和 yield ,語義更清楚了, async 表示函式裡有非同步操作, await 表示緊跟在後面的表示式需要等待結果
-
-
缺點
因為 await 將非同步程式碼改造成了同步程式碼,如果多個非同步程式碼沒有依賴性卻使用了 await 會導致效能的降低,程式碼沒有依賴性的話,完全可以使用 Promise.all 的方式
-