JavaScript中的異步編程

第一章、異步:如今與未來

一、js是單線程

瀏覽器的渲染進程是多線程的,以下:javascript

  • JS引擎線程
  • 事件觸發線程
  • 定時觸發器線程
  • 異步http請求線程
  • GUI渲染線程

而js由於防止對DOM的操做產生混亂,所以它是單線程的。單線程就是一次只能只能一個任務,有多個任務的話須要一個個的執行,爲了解決異步事件,js引擎產生了Event Loop機制。java

1.1 事件循環(EventLoop)

js引擎不是獨立運行的,它運行在宿主環境中,咱們常見的即是瀏覽器,可是隨着發展,nodej.s已經進入了服務器的領域,js還滲透到了其餘的一些領域。這些宿主環境每一個人都提供了各自的事件循環機制node

那麼什麼是事件循環機制呢?js是單線程的,單線程就是一次只能只能一個任務,有多個任務的話須要一個個的執行,爲了解決異步事件,js引擎產生了Event Loop機制。js中任務執行時會有任務隊列,setTimeout是在設定的時間後加到任務隊列的尾部。所以它雖然是定時器,可是在設定的時間結束時,回調函數是否執行取決於任務隊列的狀態。換個通俗點的話來講,setTimeout是一個「不太準確」的定時器。面試

直到ES6中,js中才從本質上改變了在哪裏管理事件循環,ES6精確得制定了事件循環的工做細節,其中最主要的緣由是Promise的引入,這使得對事件循環隊列調度的運行能直接進行精細的控制,而不像上面說到的」不太準確「的定時器。ajax

一、宏任務
  • 在 JS 中,大部分的任務都是在主線程上執行,常見的任務有:
    • 渲染事件
    • 用戶交互事件
    • js腳本執行
    • 網絡請求、文件讀寫完成事件等等。
    • setTimeout、setInterval
  • 爲了讓這些事件有條不紊地進行,JS引擎須要對之執行的順序作必定的安排,V8 其實採用的是一種隊列的方式來存儲這些任務, 即先進來的先執行。
二、微任務

(1)對每一個宏任務而言,內部有一個都有一個微任務shell

(2)引入微任務的初衷是爲了解決異步回調的問題npm

  • 將異步回調進行宏任務隊列的入隊操做。

採用改方式,那麼執行回調的時機應該是在前面全部的宏任務完成以後,假若如今的任務隊列很是長,那麼回調遲遲得不到執行,形成應用卡頓。編程

  • 將異步回調放到當前宏任務的末尾。

爲了規避第一種方式中的這樣的問題,V8 引入了第二種方式,這就是微任務的解決方式。在每個宏任務中定義一個微任務隊列,當該宏任務執行完成,會檢查其中的微任務隊列,若是爲空則直接執行下一個宏任務,若是不爲空,則依次執行微任務,執行完成纔去執行下一個宏任務。json

(3)常見的微任務有:數組

  • MutationObserver
  • Promise.then(或.reject) 以及以
  • Promise 爲基礎開發的其餘技術(好比fetch API)
  • V8 的垃圾回收過程。

咱們來看一個常見的面試題:

console.log('start'); 
setTimeout(() => { 
  console.log('timeout'); 
}); 
Promise.resolve().then(() => { 
  console.log('resolve'); 
}); 
console.log('end'); 
複製代碼
  • 先執行同步隊列的任務,所以先打印start和end
  • setTimeout 做爲一個宏任務放入宏任務隊列
  • Promise.then做爲一個爲微任務放入到微任務隊列
  • Promise.resolve()將Promise的狀態變爲已成功,即至關於本次宏任務執行完,檢查微任務隊列,發現一個Promise.then, 執行
  • 接下來進入到下一個宏任務——setTimeout, 執行

再看一個例子:

Promise.resolve().then(()=>{ 
  console.log('Promise1')   
  setTimeout(()=>{ 
    console.log('setTimeout2') 
  },0) 
}); 
setTimeout(()=>{ 
  console.log('setTimeout1') 
  Promise.resolve().then(()=>{ 
    console.log('Promise2')     
  }) 
},0); 
console.log('start'); 
 
// start 
// Promise1 
// setTimeout1 
// Promise2 
// setTimeout2 
複製代碼

接下來從js異步發展的歷史來學習異步的相關知識

第二章、回調函數

回調是js中最基礎的異步模式。

2.1 回調地獄

listen("click", function handle(evt){
	setTimeout(function request(){
		ajax("...", function response(test){
			if (text === "hello") {
				handle();
			} else {
				request();
			}
		})
	}, 500)
})
複製代碼

這種代碼經常被成爲回調地獄, 有時候也叫毀滅金字塔。由於多個異步操做造成了強耦合,只要有一個操做須要修改,只要有一個操做須要修改,它的上層回調函數和下層回調函數就須要跟着修改,想要理解、更新或維護這樣的代碼十分的困難。

2.2 信任問題

有的回調函數不是由你本身編寫的,也不是在你直接的控制下的。多數狀況下是第三方提供的。這種稱位控制反轉,就i是把本身程序的一部分執行控制交給了第三方。而你的代碼和第三方工具之間沒有一份明確表達的契約。會形成大量的混亂邏輯,致使信任鏈徹底斷裂。

第三章、Promise

回調函數的兩個缺陷:回調地獄和缺少可信任性。Promise解決了這兩個問題。

3.1 Promise的含義

Promise簡單來講就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。

  • Promise對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態:Pending(進行中)Fulfilled(已成功)Reject(已失敗)。只有異步操做的結果能夠決定當前是哪種狀態,其餘任何操做都沒法改變這個狀態。
  • 一旦狀態改變就不會再變,任什麼時候候均可以獲得這個結果。Promise的狀態改變有兩種可能:從Pending到Fulfilled和從Pending到Rejected。狀態一旦發生改變就不會再變,會一直保持這個結果。

Promise至關於購餐時的訂單號,當咱們付錢購買了想要的食物後,便會拿到小票。這時餐廳就在廚房後面爲你準備可口的午飯,你在等待的過程當中能夠作點其餘的事情,好比看個視頻,打個遊戲。當服務員喊道咱們的訂單時,咱們就能夠拿着小票去前臺換咱們的午飯。固然有時候,前臺會跟你說你點的雞腿沒有了。這就是Promise的工做方式。

3.2 基本用法

一、Promise

ES6規定,Promise對象是一個構造函數,用來生成Promise實例。

var promise = new Promise(function(resolvem reject) {
	// some code
	if (/*異步操做成功*/) {
		resolve(value);
	} else {
		reject(error);
	}
})
複製代碼
  • resolve的做用是將Promise對象的狀態從「未完成」變成「成功」,在異步操做成功時調用,並將異步操做結果做爲參數傳遞出去
  • reject的做用是將Promise對象的狀態從「未完成」變成「失敗「,在異步操做失敗時調用,並將異步操做爆出的錯誤做爲參數傳遞出去
二、resolve函數、reject函數和then()方法

Promise實例生成之後,可使用then方法分別指定Resolved狀態和Rejected狀態的回調函數

promise.then(function(value) {
	// success
}, function(error) {
	// failure
})
複製代碼
  • then方法接受兩個參數:第一個回調函數是Promise狀態變爲Resolved時調用的,第二個是Promise狀態變成Rejected時調用

  • 第二個參數是可選的,不必定要提供

  • 兩個函數都接受Promise對象傳出去的值作參數。

    • reject函數傳遞的參數一半時Error對象的實例,表示拋出錯誤。

    • resolve函數除了傳遞正常值之外,還能夠傳遞一個Promise實例

      var p1 = new Promise(function(resolve, reject) {
      	//...
      });
      
      // 這種狀況下,p1的狀態決定了p2的狀態。p2必須等到p1的狀態變爲resolve或reject纔會執行回調函數
      var p2 = new Promise(function(resolve, reject) {
      	//...
      	resolve(p1);
      });
      複製代碼

3.3 Promise.prototype.then()

then方法是定義在原型對象Promise.prototype上的。它的做用是爲Promise實例添加改變狀態時的回調函數。

  • then方法接受兩個參數:第一個回調函數是Promise狀態變爲Resolved時調用的,第二個是Promise狀態變成Rejected時調用

  • then方法返回的是一個新的Promise實例。所以能夠採用鏈式的寫法。

    promise((resolve, reject) => {
    	// ...
    }).then(() => {
    	// ...
    }).then(() => {
    	// ...
    })
    複製代碼
  • 採用鏈式的寫法能夠指定一組按照次序調用的回調函數。若是前一個回調函數返回了一個Promise實例,那麼後一個回調函數就會等待該Promise對象狀態的變化再被調用。

    promise((resolve, reject) => {
    	// ...
    }).then(() => {
    	// ...
    	return new Promise((resolve, reject) => {
    		// ...
    	})
    }).then((comments) => {
    	console.log("resolved: ", comments)
    }, (err) => {
    	console.log("rejected: ", err)
    })
    
    // 或者能夠寫的更加簡潔一些
    promise((resolve, reject) => {
    	// ...
    })
    .then(() => new Promise((resolve, reject) => {...})
    .then(
    	comments => console.log("resolved: ", comments),
    	err => console.log("rejected: ", err)
    )
    複製代碼

3.4 Promise.prototype.catch()

Promise.prototype.catch()是方法.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

getJSON('/post.json').then((posts) => {
	// ....
}).catch((error) => {
	console.log("發生錯誤", error);
})
複製代碼
  • getJSON返回一個Promise對象,若是該對象變成Resolved則會調用then()方法

  • 若是異步發生錯誤或者then方法發生錯誤,則會被catch捕捉

  • Promise在resolve語句後面再拋出錯誤不會被捕獲,由於Promise的狀態一旦改變就不會再改變了。

    var promise = new Promise((resolve, reject) => {
    	resolve('ok');
    	throw new Error('test')
    })
    promise
    	.then((value) => {console.log(value)})
    	.catch((error) => {console.log(error)})
    複製代碼
  • Promise對象的錯誤具備「冒泡」的性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch捕獲。通常來講不要再then中定義第二個函數,而老是用catch方法。

    var promise = new Promise((resolve, reject) => {
    	resolve('ok');
    	throw new Error('test')
    })
    // 不推薦
    promise
    	.then(
    		(value) => {console.log(value)},
    		(error) => {console.log(error)}
    	)
    	
    //推薦
    promise
    	.then((value) => {console.log(value)})
    	.catch((error) => {console.log(error)})
    複製代碼
  • 和傳統的try/catch不一樣,若是沒有使用catch指定錯誤處理的回調函數,promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應

  • catch返回的也是一個Promise對象,後面還能夠跟then

3.5 done()和finally()

1. done()

不管Promise對象的回調鏈是以then方法結束仍是以catch方法結束,只要最後一個方法拋出錯誤,都有可能沒法捕捉到(由於Promise內部的錯誤不會冒泡到全局)。爲此能夠提供一個done()方法,他老是在回調鏈的尾部,保證拋出任何可能出現的錯誤。

asyncFunc ()
.then(f1)
.catch(f2)
.then(f3)
.done()
複製代碼

它的源碼實現很簡單:

Promise.prototypr.done = function (onFulfilled, onRejected) {
	this.then(onFulfilled, onRejected)
	.catch(function(reason){
		// 拋出一個全局錯誤
		setTimeout(() => {throw reason}, 0)
	})
}
複製代碼
2. finally()

finally方法用於指定無論Promise對象最後如何都會執行的操做。他與done方法的最大區別在於它接受一個回調函數做爲參數,該函數無論怎麼樣都會執行。來看看它的實現方式。

Promise.prototype.finally = function (callback) {
	let P = this.constructor
    // 巧妙的使用Promise.resolve方法,達到無論前面的Promise狀態是fulfilled仍是rejected,都會執行回調函數
	return this.then(
		value => P.resolve(callback()).then(() => value),
		reason => P.resolve(callback()).then(() => throw reason)
	)
}
複製代碼

3.6 Promise.all()

Promise.all方法用於將多個Promise實例包裝成一個新的Promise實例

var p = Promise.all([p1, p2, p3])
複製代碼
  • p一、p二、p3都是Promise實例,若是不是,則會使用Promise.resolve方法,將參數轉化爲Promise實例,再進行處理

  • 該方法的參數不必定是要數組,但必需要有Iterator接口,且每一個組員都是Promise實例

  • p的狀態由p一、p二、p3決定

    • 只有p一、p二、p3的狀態都變成Fulfilled,p的狀態纔會變成Fulfilled,此時p一、p二、p3的返回值組成一個數組傳遞給p的回調函數
    • 只要p一、p二、p3有一個狀態變成Rejected,p的狀態就會變成Rejected,此時第一個Rejected的實例的返回值傳遞給p的回調函數
    var promises = [2, 3, 4, 5, 6, 7].map((id) => {
    	return getJSON(`/post/${id}.json`)
    })
    
    Promise.all(promises).then((posts) => {
    	//...
    }).catch((error) => {
    	//...
    })
    複製代碼
  • 若是做爲參數的Promise實例自身定義了catch方法,那麼它被rejected時並不會出發Promise.all()的catch方法

const p1 = new Promise((resolve, reject) => {
	resolve('hello')
})
.then(result => result)
.catch(e => e)

const p2 = new Promise(resolve, reject) => {
	throw new Error('error')
})
.then(result => result)
.catch(e => e)

const p3 = new Promise(resolve, reject) => {
	throw new Error('error')
})
.then(result => result)

// p2的catch返回了一個新的Promise實例,該實例的最終狀態是resolved
Promise.all([p1, p2])
.then(result => result)
.catch(e => e)
// ["hello", Error: error]

// p3沒有本身的catch,因此錯誤被Promise.all的catch捕獲倒了
Promise.all([p1, p3])
.then(result => result)
.catch(e => e)
// Error: error
複製代碼

3.7 Promise.race()

Promise.race方法用於將多個Promise實例包裝成一個新的Promise實例

var p = Promise.race([p1, p2, p3])
複製代碼
  • p一、p二、p3都是Promise實例,若是不是,則會使用Promise.resolve方法,將參數轉化爲Promise實例,再進行處理

  • 該方法的參數不必定是要數組,但必需要有Iterator接口,且每一個組員都是Promise實例

  • p的狀態由p一、p二、p3決定,只要p一、p二、p3有一個實例率先改變狀態,p的狀態就會跟着改變。率先改變狀態的實例的返回值傳遞給p的回調函數。

3.8 Promise.resolve()

Promise.resolve方法將現有對象轉換成Promise對象,分爲如下四種狀況:

1. 參數是一個Promise實例

Promise.resolve不作任何改變

2. 參數是一個thenable對象

thenable對象是指具備then方法的對象

let thenable = {
	then: function(resolve, reject) {
		resolve(42);
	}
}
let p1 = Promise.resolve(thenable)
p1.then(function(value) {
    console.log(value) // 42
})
複製代碼

Promise.resolve會將這個對象轉換成Promise對象,而後當即執行thenable對象的then方法

3. 參數是不具備then方法或根本不是對象

該狀況下,Promise.resolve返回一個新的Promise對象,狀態爲Resolved

var p = Promise.resolve('hello');
p.then((s) => {
	console.log(s)
})
// hello
複製代碼
4. 不帶任何參數

此狀況下,Promise.resolve方法返回一個Resolved狀態的Promise對象

console.log('start'); 
setTimeout(() => { 
  console.log('timeout'); 
}); 
Promise.resolve().then(() => { 
  console.log('resolve'); 
}); 
console.log('end'); 
複製代碼

(1)先執行同步隊列的任務,所以先打印start和end (2)setTimeout 做爲一個宏任務放入宏任務隊列 (3)Promise.then做爲一個爲微任務放入到微任務隊列 (4)Promise.resolve()將Promise的狀態變爲已成功,即至關於本次宏任務執行完,檢查微任務隊列,發現一個Promise.then, 執行 (5)接下來進入到下一個宏任務——setTimeout, 執行

3.9 Promise.reject()

Promise.reject方法會返回一個新的Promise實例,狀態爲Rejected

與Promise.resolve不一樣,Promise.reject會原封不動的將其參數做爲reject的理由傳遞給後續的方法,所以沒有那麼多的狀況分類

let thenable = {
	then: function(resolve, reject) {
		resolve(42);
	}
}

Promise.reject(thenable)
.catch(e => {
	console.log(e === thenable)
})
//true
複製代碼

第四章、Gnerator

Promise解決了回調函數的回調地獄的問題,可是Promise最大的問題是代碼的冗餘,原來的任務被Promise包裝後,不管什麼操做,一眼看過去都是許多then的堆積,原來的語義變得很不清楚。

傳統的編程語言中早有異步編程的解決方案,其中一個叫作協程,意思爲多個線程相互做用,完成異步任務。它的運行流程以下:

  • 協程A開始執行
  • 協程A執行到通常暫停,執行權交到協程B中
  • 一段時間後,協程B交還執行權
  • 協程A恢復執行
function *asyncJob () {
	// ...
	var f = yield readFile(fileA);
	// ...
}
複製代碼

它最大的優勢就是,代碼寫法很像同步操做。

4.1 Generator封裝異步任務

Generator函數是協程在ES6中最大的實現,最大的特色就是能夠交出函數的執行權。

整個Generator函數就是一個封裝的異步任務容器,異步操做須要用yield代表。Generator他能封裝異步任務的緣由以下:

  • 暫停和恢復執行
  • 函數體內外的數據交換
  • 錯誤處理機制

上面代碼的Generator函數的語法相關已經在上一篇博客中總結了,不能理解此處能夠前往復習。

Generator函數是一個異步操做的容器,它的自動執行須要一種機制,當異步操做有告終果,這種機制須要自動交回執行權,有兩種方法能夠作到:

  • 回調函數:將異步操做包裝成Thunk函數,在回調函數裏面交回執行權

  • Promise對象:將異步操做包裝成Promise對象,使用then方法交回執行權

4.2 Thunk函數

參數的求值策略有兩種,一種是傳值調用,另外一種是傳名調用

  • 傳值調用,在參數進入函數體前就進行計算;可能會形成性能損失。
  • 傳名調用,在參數被調用時再進行計算。

編譯器的傳名調用的實現將參數放到一個臨時函數之中,再將這個臨時函數傳入函數體。這個臨時函數就叫Thunk函數。

function f(m) {
	return m * 2;
}

f(x + 5);

// 等同於
var Thunk = function () {
	return x + 5;
}

function f(thunk) () {
	return thunk() * 2
}
複製代碼
1. js中的Thunk函數

js語言是按值調用的,它的Thunk函數含義和上述的有些不一樣。在js中,Thunk函數替換的不是表達式,而是多參數函數,將其替換成一個只接受回調函數做爲參數的單參數函數。

(1)在js中,任何函數,只要參數有回調函數就能夠寫成Thunk函數的形式。

// ES5
var Thunk = function (fn) {
	return function () {
		var args = Array.prototype.slice.call(arguments);
		return function (callback) {
			return function (callback) {
				args.push(callback);
				return fn.apply(this, args)
			}
		}
	}
}

// ES6
var Thunk = function (fn) {
	return function (...args) {
		return function (callback) {
			return fn.call(this, ...args, callback)
		}
	}
}

// 實例
function f (a, cb) {
    cb(a)
}
const ft = Thunk(f);
ft(1)(console.log); // 1
複製代碼

(2)生產環境中使用Thunkify模塊

$ npm install Thunkify

var thunkify = require('thunkify');
var fs = require('fs');

var read = thunkify(fs.readFile);
read('package.json')(function(err, str) {
	// ...
})
複製代碼
2. Generator函數的流程管理

前面提到了Thunk能夠用於Generator函數的自動流程管理

(1)Generator能夠自動執行

function *gen() {
	// ...
}

var g = gen();
var res = g.next();

while (!res.done) {
	console.log(res.value);
	res = g.next();
}
複製代碼

可是這不適合異步操做,若是必須知足上一步執行完成才能執行下一步,上面的自動執行就不可行。

(2)Thunk函數自動執行

var thunkify = require('thunkify');
var fs = require('fs');
var readFileThunk = thunkify(fs.readFile);

var gen = function* () {
	var r1 = yield readFileThunk('/etc/fstab');
	console.log(r1.toString());
	var r2 = yield readFileThunk('/etc/shell');
	console.log(r2.toString());
}
var g = gen();

// 將同一個函數反覆傳入next方法的value屬性
var r1 = g.next();
r1.value(function(err, data) {
	if (err) throw err;
	var r2 = g.next(data);
	r2.value(function (err, data) {
		if (err) throw err;
		g.next(data);
	})
})

// Thunk函數自動化流程管理
function run (fn) {
	var gen = fn();
	
	function next (err, data) {
		var result = gen.next(data);
		if (result.done) return;
		result.value(next)
	}
	
	next();
}

run(g)
複製代碼

上述的run函數就是以一個Generator函數自動執行器。有了這個執行器,無論內部有多少個異步操做,直接在將Generator函數傳入run函數便可,可是要注意,每個異步操做都是Thunk函數,也就是說yield後面必須是Thunk函數

4.3 co模塊

co模塊不須要編寫Generator函數的執行器

var co = require('co');
// gen函數自動執行
co(gen);
// co函數返回一個Promise對象,所以能夠用then方法添加回調
co(gen).then(function () {
    console.log('Generator函數執行完畢')
})
複製代碼
1. 基於Promise對象的自動執行
var fs = require('fs');

var readFile = function (fileName) {
	return new Promise(function (resolve, reject) {
		fs.readFile(fileName, function (error, data) {
			if (error) return reject(error);
			resolve(data);
		})
	})
}


var gen = function* () {
	var r1 = yield readFileThunk('/etc/fstab');
	console.log(r1.toString());
	var r2 = yield readFileThunk('/etc/shell');
	console.log(r2.toString());
}
var g = gen()

// 手動執行,使用then方法層層添加回調函數
g.next().value.then(function(data){
	g.next(data).value.then(function(data){
		g.next(data)
	})
})

// 根據手動執行,寫一個自動執行器
function run (gen) {
    var g = gen();
    
    function next(data) {
        var result = g.next(data);
        if (result.done) return result.value;
        result.value.then(function (data) {
            next(data);
        })
    }
    
    next();
}

run(gen)
複製代碼

第五章、async函數

ES2017標準引入了async函數,使得異步操做變得更加方便。async函數就是Generator函數的語法糖

async函數就是將Generator函數的*換成async,將yield換成await。

varasyncReadFile = async function () {
	var r1 = await readFileThunk('/etc/fstab');
	console.log(r1.toString());
	var r2 = await readFileThunk('/etc/shell');
	console.log(r2.toString());
}
複製代碼

async對於Generator的改進有三點:

  • 內置執行器:不須要像Generator函數那樣引入Thunk函數和co模塊來解決自動執行的問題
  • 適用性更廣:Generator函數中yield後只能跟Thunk函數或者Promise對象,在async函數中能夠是Promise對象和原始類型的值(數值、字符串和布爾值,但此之等同於同步操做)
  • 返回值是Promise:比Generator函數的返回值是一個Iterator對象方便了不少
1. async函數的聲明
// 函數式聲明
async function foo() {}

// 函數表達式
const foo = async function() {}

// 箭頭函數
const foo = async () => {}

// 對象方法
let obj = { async foo() {} }
obj.foo().then(...)

// class方法
class Storage {
	constructor () { ... }
	
	async getName() {}
}
複製代碼
2. 語法

(1)async函數返回一個Promise對象

  • async函數內部return語句的返回值,會成爲then方法回調函數的參數
async function f() {
	return 'hello'
}

f().then(v => console.log(v)) // hello
複製代碼
  • async函數內部拋出的錯誤會致使返回的Promise對象變成reject狀態,拋出的錯誤對象會被catch方法回調函數接收到。
async function f() {
	 throw new Error('出錯了');
}

f().then(
	v => console.log(v)
	e => console.log(e)
)
// Error: 出錯了
複製代碼
  • async函數返回的Promise對象必須等到內部全部的await命令後面的Promise對象執行完畢纔會發生狀態改變,除非遇到return語句或者拋出錯誤。

(2)await命令

  • 正常狀況下await命令後面是一個Promise對象,若是不是會被resolve當即轉成一個Promise對象

  • await命令後面的Promise對象若是變成reject狀態,則reject的參數會被catch方法的而回調函數接收到

  • 有時不但願拋出錯誤終止後面的步驟

    • 將await放在try...catch結構裏面
    • 在await後面的Promise對象後添加一個catch方法
async function f() {
	try {
		await Promise.reject('出錯了')
	} catch(e) {
	}	
	return await Promise.resolve('hello')
}

f().then( v => console.log(v)) // hello

async function f1() {
    await Promise.reject('出錯了')
		.catch(e => console.log(e));
	return await Promise.resolve('hello')
}

f1().then( v => console.log(v)) // hello
複製代碼
  • await命令只能在async函數中使用,不然會報錯

  • 若是await命令後面的異步操做不是繼發關係,最好讓他們同步觸發

let foo = getFoo();
let bar = getBar();

// 寫法1
let [foo, bar] = await Promise.all([getFoo(), getBar()])

// 寫法2
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
複製代碼

參考資料:

  • 偶像神三元的博客
  • 阮一峯老師的ES6
  • 你不知道的JavaScript(中)
相關文章
相關標籤/搜索