一. Callback (回調(diào)函數(shù))
1.定義:把函數(shù)當(dāng)作變量傳到另一個函數(shù)里渣窜,傳進去之后執(zhí)行甚至返回等待之后的執(zhí)行铺根。
2.一個簡單的例子
function add_callback(p1, p2 ,callback) {
var my_number = p1 + p2;
callback(my_number);
}
add_callback(5, 15, function(num){
console.log("call " + num);
});
3. error first
1.回調(diào)函數(shù)的第一個參數(shù)保留給一個錯誤error對象,如果有錯誤發(fā)生乔宿,錯誤將通過第一個參數(shù)err返回位迂。
2.回調(diào)函數(shù)的第二個參數(shù)為成功響應(yīng)的數(shù)據(jù)保留,如果沒有錯誤發(fā)生,err將被設(shè)置為null, 成功的數(shù)據(jù)將從第二個參數(shù)返回掂林。
4.callback hell
JavaScript 是由事件驅(qū)動的異步編程臣缀,一個異步的操作,我們在調(diào)用他的時候党饮,不會馬上得到結(jié)果肝陪,而是會繼續(xù)執(zhí)行后面的代碼。這樣刑顺,如果我們需要在查到結(jié)果之后才做某些事情的話氯窍,就需要把相關(guān)的代碼寫在回調(diào)里面,如果涉及到多個這樣的異步操作蹲堂,就勢必會陷入到回調(diào)地獄中去狼讨。eg:
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})
二. Promise(為了解決callback hell的問題)
1. Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大柒竞。ES6將其寫進了語言標(biāo)準政供,統(tǒng)一了用法,原生提供了Promise對象朽基。
2. Promise A+規(guī)范:
Promise 規(guī)范有很多布隔,如 Promise/A,Promise/B稼虎,Promise/D 以及 Promise/A 的升級版 Promise/A+衅檀,最終 ES6 中采用了 Promise/A+ 規(guī)范。Promise/A+ 官網(wǎng):https://promisesaplus.com/
3.promise對象的特點
1.對象的狀態(tài)不受外界影響霎俩,promise對象代表一個異步操作哀军,有三種狀態(tài),pending(進行中)打却、fulfilled(已成功)杉适、rejected(已失敗)柳击。只有異步操作的結(jié)果猿推,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)捌肴,這也是promise這個名字的由來“承諾”彤守;
2.一旦狀態(tài)改變就不會再變,任何時候都可以得到這個結(jié)果哭靖,promise對象的狀態(tài)改變具垫,只有兩種可能:從pending變?yōu)閒ulfilled,從pending變?yōu)閞ejected试幽。這時就稱為resolved(已定型)筝蚕。如果改變已經(jīng)發(fā)生了卦碾,你再對promise對象添加回調(diào)函數(shù),也會立即得到這個結(jié)果起宽,這與事件(event)完全不同洲胖,事件的特點是:如果你錯過了它,再去監(jiān)聽是得不到結(jié)果的坯沪。
4. 創(chuàng)造了一個Promise實例
var promise = new Promise( function( resolve, reject) {
/some code
if(//異步操作成功){
resolve(value);
}else{
reject(error);
}
});
5.Promise 方法:
Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}
Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}
1.promise.then方法返回promise的結(jié)果绿映,then 的第一個參數(shù)是處理正確時的返回值的函數(shù),第二個參數(shù)是處理錯誤時的返回的error的函數(shù)
promise.then(function(value) {
// success
}, function(error) {
// failure
});
- promise.catch可以捕獲promise返回的錯誤
var promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
- Promise.resolve 是將一個值包裹成 promise對象腐晾,狀態(tài)是fulfilled
Promise.resolve('haha')
等價于
new Promise(function (resolve, reject) {resolve('haha')})
- Promise.reject 也是將一個值包裹成 promise對象叉弦,只不過狀態(tài)是 rejected
5.Promise.all()用于將多個Promise實例,包裝成一個新的Promise實例藻糖。
var promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
var promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hehe')
}, 2000)
})
var start = Date.now()
Promise.all([promise1, promise2])
.then((res) => {
console.log(Date.now() - start)
console.log(res)
})
(1)只有promise1淹冰、promise2的狀態(tài)都變成fulfilled,p的狀態(tài)才會變成fulfilled巨柒,此時promise1樱拴、promise2的返回值組成一個數(shù)組,傳遞給回調(diào)函數(shù)洋满。
(2)只要promise1晶乔、promise2之中有一個被rejected,p的狀態(tài)就變成rejected牺勾,此時第一個被reject的實例的返回值正罢,會傳遞給新的Promise實例回調(diào)函數(shù)。
6.Promise.race方法同樣是將多個Promise實例禽最,包裝成一個新的Promise實例。但是只要promise1袱饭、promise2之中有一個實例率先改變狀態(tài)川无,新的Promise實例的狀態(tài)就跟著改變。那個率先改變的 Promise 實例的返回值虑乖,就傳遞給新的Promise實例的回調(diào)函數(shù)。
var promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
var promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hehe')
}, 2000)
})
var start = Date.now()
Promise.race([promise1, promise2])
.then((res) => {
console.log(Date.now() - start)
console.log(res)
})
6.擴展
- then 方法可以被同一個 promise 調(diào)用多次,原理在于:第一次調(diào)用之前,狀態(tài)就已經(jīng)從pengding->fulfilled娱据,同時內(nèi)部保留了一個值房交,這時多次調(diào)用.then,都會返回內(nèi)部的那個值
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
var start = Date.now()
promise.then((res) => {
console.log(res, Date.now() - start)
})
promise.then((res) => {
console.log(res, Date.now() - start)
})
promise.then((res) => {
console.log(res, Date.now() - start)
})
- 在Promise構(gòu)造函數(shù)里throw一個error糙捺,相當(dāng)于 reject(error)
- promise的 then和catch都是可以鏈式調(diào)用的诫咱,下一個then的值是上一個promise變成fulfilled的返回值(返回值可以是promise,也可以是任意值(這個任意值內(nèi)部是把它包裝成promise)。如果是promise洪灯,會等待該promise返回(狀態(tài)變更)),eg:
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
var start = Date.now()
promise
.then((res) => {
console.log(res, Date.now() - start)
})
.then((res) => {
console.log(res, Date.now() - start)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hehe')
}, 2000)
})
})
.then((res) => {
console.log(res, Date.now() - start)
})
- 構(gòu)造函數(shù)resolve 或 reject 只執(zhí)行一次坎缭,多次調(diào)用沒有任何作用
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
reject('error')
})
var start = Date.now()
promise.then(() => {
console.log('then', Date.now() - start)
})
promise.catch((e) => {
console.error(e)
console.error('catch', Date.now() - start)
})
- 值穿透,then的參數(shù)正常情況下使用接收函數(shù),如果傳遞一個非函數(shù)掏呼,則忽略坏快,下一個then使用的是當(dāng)前then的上一個返回值,也就是會跳過這個then
var promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('haha')
}, 1000)
})
promise2
.catch(1)
.catch('hehe')
.catch((error) => {
return console.log(1, error)
})
.catch((error) => {
return console.log(2, error)
})
- promise拋錯(rejected)憎夷,則跳過then,被最近的一個catch捕獲(前提是:1.then沒有第二個處理錯誤的函數(shù) 2.最近的catch沒有值穿透)莽鸿,在then/catch里throw new Error(xxx)等價于return Promise.reject(xxx)
Promise.resolve()
.then(() => {
// return new Error('error!!!') // 會打印1!J案O榈谩!
return Promise.reject(new Error('error!!!'))
})
.then((res) => {
console.log(1, res)
})
.catch((e) => {
console.error(2, e)
})
- then返回的值不能是 promise 本身鸣戴,否則會造成死循環(huán)啃沪,原因:then里返回的promise會等待他狀態(tài)改變(或者說執(zhí)行完)才會進入到下一個then
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
var a = promise.then(() => {
return a
})
a.then(console.log)
.catch(console.error)
- then方法的第二個參數(shù) vs catch方法, then里第二個處理錯誤的回調(diào)函數(shù)不會捕獲這個then第一個處理成功時的回調(diào)函數(shù)拋出的error窄锅,then/catch里沒有return值等價于return Promise.resolve(undefined)
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
promise
.then(function success(res) {
throw new Error('error')
}, function fail1(e) {
console.error(1, e)
})
.catch(function fail2(e) {
console.error(2, e)
})
三. Generator
1.Generator 函數(shù)是一個狀態(tài)機创千,封裝了多個內(nèi)部狀態(tài);執(zhí)行 Generator 函數(shù)會返回一個遍歷器對象入偷,返回的遍歷器對象追驴,可以依次遍歷 Generator 函數(shù)內(nèi)部的每一個狀態(tài)。
2.特點
- function關(guān)鍵字與函數(shù)名之間有一個星號
- 函數(shù)體內(nèi)部使用yield語句疏之,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”)殿雪。
3.yield* 語句
由于Generator函數(shù)返回的遍歷器對象,只有調(diào)用next方法才會遍歷下一個內(nèi)部狀態(tài)锋爪,所以其實提供了一種可以暫停執(zhí)行的函數(shù)丙曙。yield語句就是暫停標(biāo)志。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
(1)遇到y(tǒng)ield語句其骄,就暫停執(zhí)行后面的操作亏镰,并將緊跟在yield后面的那個表達式的值,作為返回的對象的value屬性值拯爽。
(2)下一次調(diào)用next方法時索抓,再繼續(xù)往下執(zhí)行,直到遇到下一個yield語句毯炮。
(3)如果沒有再遇到新的yield語句逼肯,就一直運行到函數(shù)結(jié)束,直到return語句為止桃煎,并將return語句后面的表達式的值篮幢,作為返回的對象的value屬性值。
(4)如果該函數(shù)沒有return語句为迈,則返回的對象的value屬性值為undefined洲拇。
(5)如果在 Generator 函數(shù)內(nèi)部奈揍,調(diào)用另一個 Generator 函數(shù),默認情況下是沒有效果的
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"
4.next()方法
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
- yield句本身沒有返回值赋续,或者說總是返回undefined男翰。
- 每次調(diào)用遍歷器對象的next方法,就會返回一個有著value和done兩個屬性的對象纽乱。value屬性表示當(dāng)前的內(nèi)部狀態(tài)的值蛾绎,是yield語句后面那個表達式的值;done屬性是一個布爾值鸦列,表示是否遍歷結(jié)束租冠。
- next方法可以帶一個參數(shù),該參數(shù)就會被當(dāng)作上一個yield語句的返回值薯嗤。
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next()
// { value: 0, done: false }
g.next()
// { value: 1, done: false }
g.next(true)
// { value: 0, done: false }
5.for...of循環(huán)
for...of循環(huán)可以自動遍歷Generator函數(shù)時生成的Iterator對象顽爹,且此時不再需要調(diào)用next方法
function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
6.Generator方法
Generator.prototype.throw()
Generator.prototype.return()
- throw()可以在函數(shù)體外拋出錯誤,然后在Generator函數(shù)體內(nèi)捕獲
var g = function* () {
try {
yield;
} catch (e) {
console.log(e);
}
};
var i = g();
i.next();
i.throw(new Error('出錯了骆姐!'));
// Error: 出錯了镜粤!(…)
- return()返回給定的值,并且終結(jié)遍歷Generator函數(shù)
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
如果return方法調(diào)用時玻褪,不提供參數(shù)肉渴,則返回值的value屬性為undefined
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return() // { value: undefined, done: true }
7. co 模塊
- co 模塊是著名程序員 TJ Holowaychuk 于2013年6月發(fā)布的一個小工具,用于 Generator 函數(shù)的自動執(zhí)行
- 用法:
Generator 函數(shù)只要傳入co函數(shù)带射,就會自動執(zhí)行同规;co函數(shù)返回一個Promise對象,因此可以用then方法添加回調(diào)函數(shù)
var co = require('co');
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
co(gen).then(function (){
console.log('Generator 函數(shù)執(zhí)行完成');
}); - 原理:
自動執(zhí)行機制:當(dāng)異步操作有了結(jié)果窟社,能夠自動交回執(zhí)行權(quán)券勺。
(1)回調(diào)函數(shù)。將異步操作包裝成 Thunk 函數(shù)灿里,在回調(diào)函數(shù)里面交回執(zhí)行權(quán)关炼。
(2)Promise 對象。將異步操作包裝成 Promise 對象钠四,用then方法交回執(zhí)行權(quán)盗扒。
四. async (需要node@8以上)
- async 函數(shù)Generator 函數(shù)的語法糖:將 Generator 函數(shù)的星號(*)替換成async跪楞,將yield替換成await缀去,僅此而已。
var fs = require('fs');
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
寫成async函數(shù)甸祭,就是下面這樣缕碎。
var asyncReadFile = async function () {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
2.用法:
async function getStockPriceByName(name) {
var symbol = await getStockSymbol(name);
var stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});
(1)async函數(shù)返回一個 Promise 對象,可以使用then方法添加回調(diào)函數(shù)
(2)async函數(shù)內(nèi)部return語句返回的值池户,會成為then方法回調(diào)函數(shù)的參數(shù)咏雌。 - await 命令
async function f() {
return await 123;
}
f().then(v => console.log(v)) // 123
(1)wait 只能在 async 函數(shù)中使用
(2)await命令后面是一個 Promise 對象凡怎。如果不是,會被轉(zhuǎn)成一個立即resolve的 Promise 對象赊抖。
(3)只要一個await語句后面的 Promise 變?yōu)閞eject统倒,那么整個async函數(shù)都會中斷執(zhí)行。
async function f() {
await Promise.reject('出錯了');
await Promise.resolve('hello world'); // 不會執(zhí)行
}
- for await...of
for await...of循環(huán)用于遍歷異步的 Iterator 接口
async function f() {
for await (const x of createAsyncIterable(['a', 'b'])) {
console.log(x);
}
}
// a
// b
- Generator + co vs async + await
(1)內(nèi)置執(zhí)行器氛雪。
Generator 函數(shù)的執(zhí)行必須靠執(zhí)行器房匆,所以才有了co模塊,而async函數(shù)自帶執(zhí)行器报亩。也就是說浴鸿,async函數(shù)的執(zhí)行,與普通函數(shù)一模一樣弦追,只要一行岳链。
(2)更好的語義。
async和await劲件,比起星號和yield掸哑,語義更清楚了。async表示函數(shù)里有異步操作寇仓,await表示緊跟在后面的表達式需要等待結(jié)果举户。
(3)更廣的適用性。
co模塊約定遍烦,yield命令后面只能是 Thunk 函數(shù)或 Promise 對象俭嘁,而async函數(shù)的await命令后面,可以是Promise 對象和原始類型的值(數(shù)值服猪、字符串和布爾值供填,但這時等同于同步操作)。
(4)返回值是 Promise罢猪。
async函數(shù)的返回值是 Promise 對象近她,這比 Generator 函數(shù)的返回值是 Iterator 對象方便多了。你可以用then方法指定下一步的操作膳帕。