將需要在異步任務后執行的操作,作為參數傳入到異步操作中,當異步操作執行完成后,調用該參數執行后面的操作
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引入的異步編程解決方案
graph LR a(回調函數)-- 復雜異步問題 -->b(回調地獄) b-->c(代碼難以閱讀和維護) c-->d(Promise)/*##回調函數異步解決方案*/ 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 的方式
-