你可能知道,Javascript語言的執行環境是"單線程"(single thread)。javascript
所謂"單線程",就是指一次只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。java
這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段Javascript代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。es6
爲了解決這個問題,Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。編程
回調是異步編程最基本的方法。api
假定有兩個函數f1和f2,後者等待前者的執行結果。promise
f1();
f2();
複製代碼
若是f1是一個很耗時的任務,能夠考慮改寫f1,把f2寫成f1的回調函數。瀏覽器
function f1(callback){
setTimeout(function () {
// f1的任務代碼
callback();
}, 1000);
}
複製代碼
執行代碼就變成下面這樣bash
f1(f2);
複製代碼
採用這種方式,咱們把同步操做變成了異步操做,f1不會堵塞程序運行,至關於先執行程序的主要邏輯,將耗時的操做推遲執行。 回調函數的優勢是簡單、容易理解和部署,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合,流程會很混亂,並且每一個任務只能指定一個回調函數。併發
Promises
對象是CommonJS
工做組提出的一種規範,目的是爲異步編程提供統一接口。異步
簡單說,它的思想是, 每個異步任務返回一個Promise對象,該對象有一個then方法,容許指定回調函數。 Promises的出現大大改善了異步變成的困境,避免出現回調地獄,嵌套層級獲得改善。
具體api的介紹請看 阮一峯 大神的 ECMAScript 6 入門 在這我舉幾個簡單的場景的實現
爲了使代碼簡介,promise的rejected
狀態的相關reject()和catch()方法省略
// 1請求
function getData1 () {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('1執行了')
resolve('請求到模擬數據1111拉')
}, 2000)
})
}
// 2請求
function getData2 (params) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('2執行了')
resolve('請求到模擬數據22222拉!params:' + params)
}, 1500)
})
}
複製代碼
1請求完成後,把1的響應參數傳入2,在發2請求
function promiseDemo () {
getData1()
.then(res => {
return getData2(res)
})
.then(res => {
console.log(res)
})
}
promiseDemo()
// 1執行了
// 2執行了
// 請求到模擬數據22222拉!params:請求到模擬數據1111拉 用時 3500 ms
複製代碼
1請求、2請求同時發,兩條響應都收到後在執行
function promiseDemo () {
Promise.all([getData1(), getData2()]).then(function (res) {
console.log(res)
})
}
// 2執行了
// 1執行了
// ["請求到模擬數據1111拉", "請求到模擬數據22222拉!params:undefined"] 用時 2000 ms
複製代碼
1請求、2請求同時發,其中一條收到請求就執行
function promiseDemo () {
Promise.race([getData1(), getData2()]).then(function (res) {
console.log(res)
})
}
// 2執行了
// 請求到模擬數據22222拉!params:undefined 用時 1500 ms
// 1執行了
複製代碼
由此Promise對象仍是很好用的,對於異步的流程的控制獲得了大大改善,經過
.then()
的方法可進行鏈式調用。 但是.then() .catch()
的使用也致使代碼很是難看,嵌套也很深,因此async/await
就出來了
Async/await 是Javascript編寫異步程序的新方法。以往的異步方法無外乎回調函數和Promise。可是Async/await創建於Promise之上。
咱們仍是來看一 看阮一峯 大神的 ECMAScript 6 入門 的例子
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
複製代碼
上面代碼指定50毫秒之後,輸出hello world。 進一步說,async函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖
咱們看具體的示例
1請求完成後,把1的響應參數傳入2,在發2請求
上文中的promise 實現方法是經過then的鏈式調用,可是採用async會更加簡潔明瞭
async function asyncDemo () {
const r1 = await getData1()
const r2 = await getData2(r1)
console.log(r2)
}
// 1執行了
// 2執行了
// 請求到模擬數據22222拉!params:請求到模擬數據1111拉 用時 3500 ms
複製代碼
用同步的書寫方式實現了異步的代碼。等待getData1的異步函數執行完了後發返回值賦值給r1,傳入r2,在執行r2
1請求、2請求同時發,規定請求到達的順序
假如咱們有一種這樣的業務需求,併發兩個請求,可是要規定收到請求的順序應該怎麼作的?這裏仍是借鑑阮一峯大神的代碼
async function asyncDemo2 () {
const arr = [getData1, getData2]
const textPromises = arr.map(async function (doc) {
const response = await doc()
return response
})
// 按次序輸出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}
// 2執行了 (由於2是 1500ms後執行) 因此2先執行
// 1執行了
// 請求到模擬數據1拉 (for .. of )規定了輸出的順序
// 請求到模擬數據22222拉!params:undefined
複製代碼
面代碼中,雖然map方法的參數是async函數,但它是併發執行的,由於只有async函數內部是繼發執行,外部不受影響。後面的for..of循環內部使用了await,所以實現了按順序輸出
怎麼樣這麼BT的需求都能實現把。
他使得異步代碼變的再也不明顯也是一點弊端咯,不過根據實際狀況選擇最合適的異步編程纔是最好的選擇。async 是 Generator 函數的語法糖。因此想更深刻的理解其中內部原理的趕忙去看看 Generator
函數把