javascript與其他語言的經典不同在於,javascript是非同步的,而其他語言是同步的。這裡,我們介紹一下javascript中非同步的幾種方式。
幾種非同步方式
- 回撥函式
- promise
- generator
- async / await
回撥函式
回撥函式,是早期js中廣泛使用的一種回撥方式,jquery中的ajax方法就是經典的回撥函式模式。
回撥函式的寫法中,回撥是放在函式引數裡面的,執行的過程看起來沒有同步寫法那麼明瞭。
在回撥次數多的情況下,可能會造成回撥地獄,就是巢狀太深。所以es後面提出的非同步方案也是不斷對非同步做優化。
$.ajax({
url: 'http://binnie.com/getdata',
type: 'GET',
data: {},
success: {
// success todo
},
error: {
// error todo
}
})
複製程式碼
promise
promise,是es6中提供的一種解決方案,相比於回撥函式,promise的寫法更加同步化一些~
狀態
promise的狀態分為三種:
- pending
- resolved
- rejected
狀態的變化只有兩個過程,並且狀態一旦發生改變,就永遠不可逆。
- pending -> resolved
- pending -> rejected
基礎
Promise是一個建構函式,我們可以使用new來進行例項化的操作。
我們來看一個簡單的?
我們可以看到new一個Promise物件的時候,傳入的是一個函式,該函式攜帶兩個引數:resolve&reject,分別為成功操作&失敗操作,函式內部的操作是同步執行的,所以當執行程式碼的時候,馬上就可以列印出1
new返回的物件是一個Promise物件,Promise物件有幾個內建的函式,這幾個函式是在Promise的同步函式執行完之後可能觸發的函式。
每次執行完其中一個函式,是可以接著鏈式執行的,因為每次執行完一次,都會返回一個新的Promise物件。
- then
- finally
- catch
let promise = new Promise((resolve, reject) => {
// 這裡是同步執行的
console.log(1)
// 非同步操作
return resolve('binnie')
});
promise.then((resolve) => {
// 成功回撥,即執行resolve()
console.log('then')
},(reject) => {
// 失敗回撥,即執行reject()
console.log('err')
}).finally(() => {
// 完成回撥,無論成功/失敗都會到的回撥
console.log('finally')
}).catch((err) => {
// 錯誤回撥,前面任何一步丟擲的錯誤
console.log(err)
});
// 1
// then
複製程式碼
例項方法
例項方法是new Promise返回的物件的原型方法。比如then方法 -> Promise.prototype.then。
then
then方法接受兩個引數,第一個為resolve回撥,第二個為reject回撥
promise.then((resolve) => {
// resolve引數為promise resolve的時候的引數
// 當promise執行resolve時,會進入該回撥
},(reject) => {
// reject引數為promise reject的時候的引數
// 當promise丟擲異常 或者 執行reject時,會進入該回撥
})
複製程式碼
finally
finally回撥函式不接受任何引數,只做finally處理,類似請求的complete操作。
promise.finally(() => {
// resolve or reject 之後的操作
})
複製程式碼
catch
catch方法,用於捕獲異常。
在promise中的異常可以在兩個位置進行捕獲,then方法的第二個函式 or catch方法。一般情況下,我們會在catch中捕獲。
在promise中,錯誤也是會冒泡的,如果then操作沒有新增reject方法,那麼錯誤就會在後面的catch捕獲,不過,如果沒有對獲取進行捕獲,錯誤其實會被promise自己吃掉,而不會暴露在最外面。
promise.then(() => {
console.log('then')
throw new Error()
}).catch((err) => {
console.log(err)
})
複製程式碼
Promise方法
Promise方法,直接呼叫即可,返回的也是promise物件。
Promise.all(array)
Promise.all,接受一個陣列做為引數,引數中的物件會被轉化為promise物件
當陣列中的所有promise物件resolve時,all的promise的狀態才變為resolve
當陣列中的任一promise物件reject時,all的promise的狀態就變為reject
Promise.all 類似我們平常使用的 &&
Promise.all([promise,2,3]).then((resolve) => {
// 引數也是一個陣列,為all陣列對應的resolve引數
// ['binnie',2,3]
console.log(resolve)
}).catch((err) => {
// 錯誤為第一個reject時丟擲的錯誤
console.log(err)
})
複製程式碼
Promise.race(array)
Promise.race跟all的用法類似,也是接受一個陣列做為引數
陣列中最先改變狀態的,那個狀態就會同步給race的狀態
Promise.race([1,promise,3]).then((resolve) => {
// 引數為第一個狀態變為resolve的引數
console.log(resolve)
}).catch((err) => {
// 引數為第一個狀態變為reject的引數
console.log(err)
})
複製程式碼
Promise.resolve()
返回一個狀態為resolved的promise物件。
Promise.resolve()可攜帶引數,引數會做為返回物件的引數。
Promise.reject()
返回一個狀態為rejected的promise物件。
Promise.reject()可攜帶引數,引數會做為返回物件的引數。
關於promise,也可檢視樓主之前的文章 ES6之promise基礎篇
generator
generator可以理解為一個暫停執行函式。每次執行next()來往下走,直到結束,狀態停止。
generator函式的寫法,就是在函式後面加一個星號 function*
,在函式內部,可以使用 yield
來進行暫停的操作。
function* foo() {
console.log(0)
yield 1
yield 2
yield 3
return 4
yield 5
}
// 呼叫generator函式的時候,函式並不會被執行
let f = foo()
console.log(f)
// 呼叫next時函式才會開始執行,當遇到yield時暫停
// 下一次next時會從上次暫停的位置繼續執行
// 當遇到return時 or 函式執行完成時,函式結束
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())
複製程式碼
async / await
generator相對來說可讀性會比較差,於是乎有來async函式,表示函式內部會有非同步操作,async函式相當於是generator函式的語法糖。
下面我們來看下async函式的寫法
// 定義一個async函式,表明函式內部要執行非同步操作
async function fun() {
// await後面跟著非同步操作
// 函式會等待await操作完成之後再繼續往下走
let obj = await request();
return obj;
}
// async函式執行完之後會返回一個promise物件,函式返回值可再then中取到
let ret = fun()
ret.then((resolve) => {
console.log(resolve)
})
複製程式碼
當async函式中遇到await時,就會進行等待,然後再執行下一步的操作,如果沒必要序列,我們可以改成並行的寫法,這樣可以節省函式執行時間。
// 序列寫法
async function fun() {
let obj1 = await request1();
let obj2 = await request2();
return;
}
// 並行寫法
async function fun() {
let obj1 = request1();
let obj2 = request2();
let ret1 = await obj1;
let ret2 = await obj2;
return;
}
// Promise.all寫法
async function fun() {
let [obj1, obj2] = await Promise.all([request1(), request2()]) ;
return;
}
複製程式碼
寫在最後
非同步操作的核心都是一樣的,只是寫法不同而已。
具體要採用哪種非同步方法,還是要看我們實際的專案需要。