在JavaScript語言中,代碼都是是單線程執行的,正是因爲這個緣由,致使了JavaScript中全部的網絡操做,瀏覽器事件,都必須知足異步執行的要求。因此異步的各類方案開始出現並逐步合理化,簡單話!api
在開發過程當中你們使用的異步處理方案通常包括:回調函數(Callback)
、Promise
、Generator
函數、async/await
。這裏就主要說一下這些方案的異同:數組
假設咱們定義一個getData
函數用於數據請求:promise
function getData(url, callback) {
// 模擬數據請求
setTimeout(() => {
let res = {
url: url,
data: {}
}
callback(res)
}, 1000)
}
複製代碼
如今的需求是咱們須要依次請求三次服務器,而且每次請求的數據必須在上次成功的基礎上執行:瀏覽器
getData('/api/page/1?params=123',(res1) => {
console.log(res1);
getData(`/api/page/2?params=${res1.data.params}`, (res2) => {
console.log(res2);
getData(`/api/page/3?params=${res2.data.params}`, (res3) => {
console.log(res3);
})
})
})
複製代碼
經過上面的🌰,咱們能夠看到第一次的url:/api/page/1?params=123
,第二次的url: /api/page/2?params=${res1.data.params}
,依賴第一次請求的數據,第三次的url:/api/page/2?params=${res2.data.params}
,依賴第二次請求的數據。因爲咱們每次的數據請求都依賴上次的請求,因此咱們將會將下一次的數據請求以回調函數的形式寫在函數內部,這其實就是咱們常說的回掉地獄
!服務器
一樣的需求,咱們使用Promise
,去實現看看:網絡
首先咱們須要先將咱們的getData
函數改寫成Promise
的形式異步
function getDataPromise(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = {
url: url,
data: {}
}
resolve(res)
}, 1000)
})
}
複製代碼
那麼邏輯代碼應該變成:async
getDataPromise('/api/page/1?params=123')
.then(res1 => {
console.log(res1);
return getDataPromise(`/api/page/2?params=${res1.data.params}`)
})
.then(res2 => {
console.log(res2);
return getDataPromise(`/api/page/3?params=${res2.data.params}`)
})
.then(res3 => {
console.log(res3);
})
複製代碼
這樣寫完來看,發現咱們每次在數據請求成功(then
)以後返回一個Promise
對象,方便下次使用,這樣咱們就避免了回掉地獄
的出現,可是這樣其實也不算事完美,當咱們的請求變得複雜的時候咱們會發現咱們的代碼會變的更加複雜。函數
爲了不這種狀況的出現 async/await
應運而生。ui
getData
函數不變,仍是Promise
function getDataPromise(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = {
url: url,
data: {}
}
resolve(res)
}, 1000)
})
}
複製代碼
需求代碼變成:
async function getData () {
let res1 = await getDataPromise('/api/page/1?params=123');
console.log(res1);
let res2 = await getDataPromise(`/api/page/2?params=${res1.data.params}`);
console.log(res2);
let res3 = await getDataPromise(`/api/page/2?params=${res2.data.params}`);
console.log(res3);
}
複製代碼
怎麼樣,是否是這段代碼閱讀起來很是舒服,其實async/await
都是基於Promise
的,使用async
方法最後返回的仍是一個Promise
;實際上async/await
能夠看做是Generator
異步處理的語法糖,👇咱們就來看一下使用Generator
怎麼實現這段代碼
// 異步函數依舊是Promise
function getDataPromise(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let res = {
url: url,
data: {}
}
resolve(res)
}, 1000)
})
}
function * getData() {
let res1 = yield getDataPromise('/api/page/1?params=123');
console.log(res1);
let res2 = yield getDataPromise(`/api/page/2?params=${res1.data.params}`);
console.log(res2);
let res3 = yield getDataPromise(`/api/page/2?params=${res2.data.params}`);
console.log(res3);
}
複製代碼
其實能夠分開來看:
let fn = getData()
fn.next().value.then(res1 => {
fn.next(res1).value.then(res2 => {
fn.next(res2).value.then( () => {
fn.next()
})
})
})
複製代碼
上面的代碼咱們能夠看到,next()
每一步之行.value
方法返回的都是一個Promise
,因此咱們能夠在後面添加then
方法,在then
方法後面我繼續調用next()
,知道函數運行完成。實際上上面的代碼咱們不須要手動去寫,咱們能夠對其封裝一下:
function run(gen) {
let fn = gen()
function next(data) {
let res = fn.next(data)
if (res.done) return res.value
res.value.then((info) => {
next(info)
})
}
next()
}
run(getData)
複製代碼
run
方法用來自動執行一步操做,其實就能夠看做是Generator
在進行遞歸
操做;
這樣咱們就將異步操做封裝到了函數內部,其實不難發現async/await
和Generator
有不少類似的地方,只不過async/await
在語義上更容易被理解。
在使用async/await
的時候咱們不須要在去定義run()
,它內部已經給咱們定義封裝好了,這也是爲何說async/await
是Generator
異步處理的語法糖了。
上面咱們介紹了回調函數(Callback)
、Promise
、Generator
函數、async/await
的區別,下面咱們就來具體說說Promise
。
then
和 Promise.prototype.catch()
方法都會返回 promise
,它們能夠被鏈式調用 — 一種稱爲複合composition
的操做.
第一個參數:狀態從 pending
-> fulfilled
時的回調函數
第二個參數:狀態從 pending
-> rejected
時的回調函數
返回值:新的 Promise
實例(注意不是原來的 Promise
實例)
特色
因爲 then
方法返回一個新的 Promise
實例,因此 then
方法是能夠鏈式調用的,鏈式調用的 then
方法有兩個特色:
第一:後一個 then
方法的回調函數的參數是前一個 then
方法的返回值
第二:若是前一個 then
方法的返回值是一個 Promise
實例,那麼後一個 then
方法的回調函數會等待該 Promise
實例的狀態改變後再執行
catch 方法能夠用於您的promise組合中的錯誤處理。
Internally calls Promise.prototype.then on the object upon which is called, passing the parameters undefined and the onRejected handler received; then returns the value of that call (which is a Promise).
你們能夠看一下下面的代碼:
const promise = new Promise(function (resolve, reject) {
setTimeout(() => {
reject('err')
}, 1000)
})
promise.then(
res => console.log('s1'),
err => console.log('e1')
).then(
res => console.log('s2')
).catch(
err => console.log('e2')
)
複製代碼
e1
s2
複製代碼
能夠發現,在第一個 then
方法執行的錯誤處理函數中捕獲到了錯誤,因此輸出了 e1
,那麼這個錯誤已經被捕獲到了,也就不須要 catch
再次捕獲了,因此沒有輸出 e2
,這是正常的,但問題是居然輸出了 s2
。。。。 因此爲了不這種狀況代碼應該改成:
promise.then(
res => console.log('s1')
).then(
res => console.log('s2')
).catch(
err => console.log('e2')
)
複製代碼
這樣只會輸出e2
了
當咱們想在Promise
不管成功仍是失敗的時候都想進行某一步操做時,能夠說使用finally
promise.then(
res => console.log('s1')
).catch(
err => console.log('e1')
).finally(
() => console.log('end')
)
複製代碼
很容易可以發現,.finally
只不過是一個成功與失敗的回調函數相同的 .then
而已。
參數(iterable) 一個可迭代的對象,如 Array 或 String;
返回值
🌰:
const p = Promise.all([promise1, promise2, promise3])
p.then(
(res) => {
// res 是結果數組
}
)
複製代碼
只有當全部
Promise
實例的狀態都變爲fulfilled
,那麼Promise.all
生成的實例纔會fulfilled
。 只要有一個Promise
實例的狀態變成rejected
,那麼Promise.all
生成的實例就會rejected
。
做用:與 Promise.all
相似,也是將多個 Promise
實例包裝成一個 Promise
實例。
參數:與 Promise.all
相同
特色:
Promise.race
方法生成的 Promise
實例的狀態取決於其所包裝的全部 Promise
實例中狀態最早改變的那個 Promise
實例的狀態。
race 函數返回一個 Promise,它將與第一個傳遞的 promise 相同的完成方式被完成。它能夠是完成( resolves),也能夠是失敗(rejects),這要取決於第一個完成的方式是兩個中的哪一個。 若是傳的迭代是空的,則返回的 promise 將永遠等待。 若是迭代包含一個或多個非承諾值和/或已解決/拒絕的承諾,則 Promise.race 將解析爲迭代中找到的第一個值。
const promise = Promise.race([
getData('/path/data'),
new Promise((resolve, reject) => {
setTimeout(() => { reject('timeout') }, 10000)
})
])
promise.then(res => console.log(res))
promise.catch(msg => console.log(msg))
複製代碼
做用:將現有對象(或者原始值)轉爲 Promise
對象。
參數:參數能夠是任意類型,不一樣的參數其行爲不一樣
Promise
對象,則原封不動返回thenable
對象(即帶有 then
方法的對象),則 Promise.resolve
會將其轉爲 Promise
對象並當即執行 then
方法Promise.resolve
會將其包裝成 Promise
對象,狀態爲 fulfilled
fulfilled
的 Promise
對象Promise.reject(reason)
方法返回一個帶有拒絕緣由reason參數的Promise對象。
通常經過使用Error的實例獲取錯誤緣由reason對調試和選擇性錯誤捕捉頗有幫助。
Promise.reject('err')
// 等價於
new Promise(function (resolve, reject) {
reject('err')
})
複製代碼
其實咱們在js中能夠將同步代碼也可以使用Promise
function a() {
console.log('aaa')
}
// 等價於
const p = new Promise((resolve, rejext) => {
resolve(a())
})
複製代碼
或者點擊Promise