基本概念
傳統(tǒng)JavaScript異步編程的形式大體分以下幾種锰什。
- 回調(diào)函數(shù)
- 事件監(jiān)聽
- 發(fā)布/訂閱
- Promise 對象
異步
一個任務(wù)連續(xù)的執(zhí)行就叫做同步下硕。如果將任務(wù)為分兩步執(zhí)行,執(zhí)行完第一步汁胆,轉(zhuǎn)而執(zhí)行其它任務(wù)梭姓,等做好了準(zhǔn)備,再回過頭執(zhí)行第二步嫩码,這種不連續(xù)的執(zhí)行就叫做異步誉尖。
回調(diào)函數(shù)
回調(diào)函數(shù)就是把第二步執(zhí)行的任務(wù)單獨(dú)寫在一個函數(shù)里面,等到重新執(zhí)行這個任務(wù)的時候铸题,就直接調(diào)用這個函數(shù)铡恕。回調(diào)函數(shù)的英語叫callback
丢间,直譯過來就是"重新調(diào)用"探熔。
loadData(url, function (data) {
console.log(data);
});
注意:任務(wù)第一步執(zhí)行完后,所在的上下文環(huán)境就已經(jīng)結(jié)束了烘挫,所以我們一般會使用var that = this
將第一步執(zhí)行時的this
指向進(jìn)行保存诀艰,以便回調(diào)時使用。
function Api(url) {
this.url = url;
this.request = function () {
var that = this
setTimeout(function () {
console.log('url', that.url)
}, 1000)
}
}
var api = new Api('http://127.0.0.1')
api.request() // url http://127.0.0.1
Generator函數(shù)
異步編程解決方案中饮六, ES6還提供了Generator函數(shù)其垄。它其實是一個普通函數(shù),獨(dú)有特征
-
function
關(guān)鍵字與函數(shù)名之間有一個星號卤橄; - 函數(shù)體內(nèi)部使用
yield
表達(dá)式绿满,定義不同的內(nèi)部狀態(tài)。
function* statusGenerator() {
yield 'pending';
yield 'running';
return 'end';
}
var st = statusGenerator();
上面代碼 statusGenerator
函數(shù)返回一個迭代器對象窟扑,函數(shù)內(nèi)定義了三個狀態(tài)棒口,調(diào)用迭代器next
方法指向下一個狀態(tài)寄月。
st.next() // { value: 'pending', done: false }
st.next() // { value: 'running', done: false }
st.next() // { value: 'end', done: false }
yield 表達(dá)式
yield
表達(dá)式就是暫停標(biāo)志。迭代器執(zhí)行next
時无牵。
- 遇到
yield
表達(dá)式,就暫停執(zhí)行后面的操作厂抖,并將yield
后面的那個表達(dá)式的值作為返回的對象的value
屬性值茎毁。 - 下一次調(diào)用
next
方法時,再繼續(xù)往下執(zhí)行忱辅,直到遇到下一個yield
表達(dá)式七蜘。 - 如果沒有再遇到新的
yield
表達(dá)式,就一直運(yùn)行到函數(shù)結(jié)束墙懂,直到return
語句為止橡卤,并將return
語句后面的表達(dá)式的值,作為返回的對象的value
屬性值损搬。 - 如果該函數(shù)沒有
return
語句碧库,則返回的對象的value
屬性值為undefined
。
for...of 循環(huán)
我們也可以使用 for...of
進(jìn)行遍歷巧勤。
function* statusGenerator() {
yield 'pending';
yield 'running';
return 'end';
}
var st = statusGenerator();
for(let v of st){
console.log(v)// pending running
}
Generator 的應(yīng)用
協(xié)程
協(xié)程的意思是多個線程互相協(xié)作嵌灰,完成異步任務(wù)。它是一些編程語言的異步編程方案颅悉,比如go中的協(xié)程實現(xiàn)goroutine
沽瞭。協(xié)程序執(zhí)行的大致流程如下:
- 協(xié)程
A
開始執(zhí)行。 - 協(xié)程
A
執(zhí)行到一半剩瓶,進(jìn)入暫停驹溃,執(zhí)行權(quán)轉(zhuǎn)移到協(xié)程B
。 - (一段時間后)協(xié)程
B
交還執(zhí)行權(quán)延曙。 - 協(xié)程
A
恢復(fù)執(zhí)行豌鹤。
JavaScript中的協(xié)程實現(xiàn)Generator
函數(shù),它可以在指定的地方(yield
)交出函數(shù)的執(zhí)行權(quán)(即暫停執(zhí)行)搂鲫,然后等待執(zhí)行權(quán)交還繼續(xù)執(zhí)行傍药。
比如:我們實現(xiàn)一個倒計時函數(shù),任務(wù)就緒后等待倒計時魂仍,一起執(zhí)行拐辽。
function* countdown(num, running) {
do {
yield num--
} while (num > 0)
running()
}
const tasks = []
const ct = countdown(3, function () {
console.log('start run task')
for (let task of tasks) {
task()
}
})
for (let i = 0; i < 3; i++) {
tasks.push(function () {
console.log('task '+ i)
})
ct.next()
}
ct.next()
一個異步請求封裝
var fetch = require('node-fetch');
function* request(){
var url = 'xxxx';
var user = yield fetch(url); // 返回promise對象,data: {'user':'xxxx'}
console.log(user);
}
var req = request();
var result = req.next();
result.value.then(function(data){
return data.user
}).then(function(user){
req.next(user); // 將 user信息傳到 request()函數(shù)擦酌,被user變量接收俱诸。
});
async函數(shù)
ES2017 引入了 async
和await
關(guān)鍵字,使用這對關(guān)鍵字赊舶,可以用更簡潔的方式寫出基于Promise
的異步行為睁搭,而無需刻意地鏈?zhǔn)秸{(diào)用promise
赶诊。
async
聲明的函數(shù)一般稱為async
函數(shù)≡奥妫可以把 async 看作是 Generator 的語法糖舔痪,因為它們本質(zhì)的作用一樣。
Generator 寫法
const loadData = function (url) {
return new Promise(function (resolve, reject) {
resolve(data);
});
};
const request = function* () {
const user = yield loadData('https://user');
const goods = yield loadData('https://goods');
console.log(user, goods);
};
async 寫法
const loadData = function (url) {
return new Promise(function (resolve, reject) {
resolve(data);
});
};
const request = async function () {
const user = await loadData('https://user');
const goods = await loadData('https://goods');
console.log(user, goods);
};
基本用法
async
函數(shù)會返回一個 Promise 對象锌唾。當(dāng)函數(shù)執(zhí)行的時候锄码,一旦遇到await
就會先返回,等到異步操作完成晌涕,再接著執(zhí)行函數(shù)體內(nèi)后面的語句滋捶。
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello', 50);
async
函數(shù)內(nèi)部return
語句返回的值,會成為then
方法回調(diào)函數(shù)的參數(shù)余黎。
async function hello() {
return 'hello';
}
hello().then(v => console.log(v))
// "hello"
async
函數(shù)內(nèi)部拋出錯誤重窟,會導(dǎo)致返回的 Promise 對象變?yōu)?code>reject狀態(tài)。拋出的錯誤對象會被catch
方法回調(diào)函數(shù)接收到惧财。
async function hello() {
throw new Error('Error');
}
hello().then(
v => console.log(v),
e => console.log( e)
) // //Error: Error
await 命令
一般情況下巡扇,await
后面都是一個 Promise 對象,返回該對象的結(jié)果可缚。如果不是 Promise 對象霎迫,就直接返回對應(yīng)的值。
async function hello() {
return await 'hello'
}
hello().then(v => console.log(v)) // hello
async function hello() {
return await Promise.resolve('hello');
}
hello().then(v => console.log(v)) // hello
錯誤處理
如果await
后面的異步操作出錯帘靡,那么等同于async
函數(shù)返回的 Promise 對象被reject
知给。
async function hello() {
await new Promise(function (resolve, reject) {
throw new Error('error');
});
}
hello()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:error
所以最好把 await
命令放在try...catch
代碼塊中。
async function hello() {
try {
await new Promise(function (resolve, reject) {
throw new Error('error');
});
} catch(e) {
console.log('err:', e) // error
}
return await('hello');
}
const h = hello();
h.then((v) => {console.log(v)}) // hello
小結(jié)
本文記錄了JavaScript異步編程中的一些方式描姚,Generator
函數(shù)和 async
和await
語法涩赢,歡迎留言交流。