箭頭函式和this
寫Promise的時候,自然而然會使用箭頭函式的撰寫方式,箭頭函式就是.Neter們熟知的lambda函式,已經被大部分主流語言支持,也受到了廣大碼農的交口稱贊,但是Jser們卻會遇到不大不小的一個坑,
眾所周知,js函式中的this由呼叫它的背景關系決定,我們還可以通過apply、call、bind等方式系結背景關系,從而改變函式中this的運行時指向,而當this遇到lambda時,又有所不同,
function Test() {
this.name = "alice"
}
Test.prototype = {
normal: function () {
console.info(this.name)
},
lambda: () => {
console.info(this.name)
}
}
let test = new Test()
test.normal() //輸出:alice
test.lambda() //輸出:undefined
為什么會這樣?網上很多分析,讓人云里霧里,其實只要了解了——lambda與普通方法一個主要區別就是它能保持外層背景關系變數的參考——這個特性就明白了,用lambda在方法內寫過嵌套區域方法的.Neter很容易理解這個說法,
private Action Test()
{
var name = "alice";
Action action = () => Console.WriteLine(name);
//name將被捕獲,會一直生存到action被回收
return action;
}
so,可以將js的箭頭函式理解為受運行時外層影響的內嵌函式,它是在運行時賦值的——以上述js代碼為例,js解釋器解釋Test.prototype的定義,解釋到normal函式時,是不care其內部邏輯的,繼續往下解釋到lambda函式時,會過一遍其內部參考到的外部變數,若有則捕獲用于真正執行時(所謂詞法作用域),此時這個this指的是運行環境的根物件(在瀏覽器中可能就是window物件),而不是test物件(此時還不存在噻),注:本段為個人理解,
再看一個代碼片段,請讀者自行嘗試分析下:
var alice = {
age: 18,
getAge: function () {
var aliceAge = this.age;//this是alice
var getAgeWithLambda = () => this.age;//this還是alice
var getAgeWithFunction = function () {
return this.age;// this是window
}
return[aliceAge,getAgeWithLambda(),getAgeWithFunction()]
}
}
console.info(alice.getAge()) //輸出:[18, 18, undefined]
Promise
Promise主要是將原本的callback變為then,寫出來的代碼更便于閱讀,有多種方式得到一個Promise物件:
Promise.resolve():Promise.resolve('foo')等價于new Promise(resolve => resolve('foo'))- 執行
async修飾的函式:
async function newPromise(){}
let p = newPromise() //p就是Promise物件
如果async函式中是return一個值,這個值就是Promise物件中resolve的值;如果async函式中是throw一個值,這個值就是Promise物件中reject的值,
- 直接構造:
let p = new Promise((resolve, reject)=>{})
注意,構造Promise時內部代碼已經開始執行,只是把resolve部分掛起放到后面執行,測驗代碼如下:
let p = new Promise((resolve, _) => {
resolve(1);
console.info(2); //率先執行
});
console.info(3);
p.then(num => {
console.info(num); //后置執行
});
console.info(4);
//輸出:2 3 4 1
所以,這跟慣常認為的整個Promise代碼塊都后置執行不一樣,需要注意,
我們可以如上述將回呼邏輯寫在then里,也可以將邏輯移到外層變為同步執行(而非后置執行),這就需要用到await關鍵字了,它將阻塞當前代碼塊,等待resolve塊執行完再往后執行,代碼如下:
async function test() {
let p = new Promise((resolve, _) => {
resolve(1);
console.info(2);
});
console.info(3);
let num = await p;
console.info(num);
console.info(4);
}
test()
//輸出:2 3 1 4
ES6引入的Generator函式,是async/await的基礎,
await讓我們能用同步寫法寫出異步方法,但事實真的如此嗎?在C#領域,這么說尚且沒錯,后端語言大多支持多執行緒和執行緒池,await雖然阻塞了后續代碼的執行,但只是背景關系被掛起,執行緒本身是不會被阻塞的還可以干其它事情,await回傳后甚至還可以讓其它執行緒接手,可參看本人以前的博文async、await在ASP.NET[ MVC]中之執行緒死鎖的故事,js的話,它是單執行緒,而且它也不像go一樣有完善的協程機制,無法手動(time.sleep()、select{}等)切換代碼塊執行——除非等到await回傳,否則執行緒是沒機會執行其它代碼塊的, 錯誤,
注意await掛起的不是執行緒,而是resolve背景關系,推測本質上還是與js的執行佇列相關,只不過await后續邏輯都排在resolve之后罷了,
async function test() {
let p = new Promise((resolve, _) => {
setTimeout(() => {
resolve(1)
}, 5000)
});
setTimeout(() => {
console.info(2)
}, 3000)
let num = await p
console.info(num)
}
test()
//輸出:2 1
但使用await時仍要注意避免不必要的等待,如果前后幾個Promise沒有依賴關系(更精確的說法是,任務的發起條件不依賴其它任務的結果),那么最好同時發起它們,并在最后await Promise.all(promises),
例外捕獲
很多文章都說try/catch在異步模式下無效,其實搭配await的話還是可以的(畢竟await可以使得回呼執行在try塊內),如下:
let testPromise = function () {
// throw new Error("異步例外測驗")
return Promise.reject(new Error("異步例外測驗"))
}
let testInvocation = async () => {
try {
await testPromise()
} catch (err) {
console.error(`catch: ${err}`)
}
}
testInvocation() //輸出:catch: Error: 異步例外測驗
如果try的是整個testInvocation()那自然沒戲,
如果覺得在每個異步方法內部try/catch太繁瑣,那么可以抽離出一個模板方法,或者使用process物件注冊uncaughtException和unhandledRejection事件,注意這兩者的區別:
process.on('uncaughtException', e => {
console.error(`uncaughtException: ${e.message}`)
});
process.on('unhandledRejection', (reason, promise) => {
console.error(`unhandledRejection: ${reason}`)
});
let testPromise = function(){
throw new Error("異步例外測驗")
}
testPromise() //輸出:uncaughtException: 異步例外測驗
let testInvocation = async () => await testPromise() //.catch 因為testPromise()回傳的不是Promise,所以catch無效
testInvocation() //輸出:unhandledRejection: Error: 異步例外測驗
//注意兩次例外型別不一樣
如果你使用electron開發桌面應用,可能無法[以process.on('unhandledRejection', ...)方式]捕獲unhandledRejection例外(本人使用v10.1.0版本測驗發現),遇到這種情況,只能老老實實在每個Promise后面寫catch(),
使用process捕獲例外無法獲取例外的背景關系,且丟失背景關系堆疊使得node不能正常進行記憶體回收,從而導致記憶體泄露,
node中還有個東西domain用于彌補process的問題,但是個人認為domain使用不便,且織入業務代碼程度過深,另外據說目前版本還不穩定(后續可能會更改),甚至有文章說已被node廢棄,具體什么情況暫未深入了解,總之希望node或者js平臺能出一個關于例外捕獲的更好的解決方案,
協程安全
在js場景下,異步機制更類似于Go的協程(畢竟js是單執行緒,多執行緒無從談起),所以此處取名為協程安全,
直接看代碼:
let policy = {}
let testfun = async () => {
let data = https://www.cnblogs.com/newton/p/await policy
//生成亂數
data["key"] = utility.getRandomString(20)
return data
}
//1
let testinfo = async () => {
let data = await testfun()
console.info(data.key)
}
for (let i = 0; i < 5; i++) {
testinfo()
}
//輸出結果是5次相同的亂數
//2
let testinfo2 = async () => {
for (let i = 0; i < 5; i++) {
let data = await testfun()
console.info(data.key)
}
}
testinfo2()
//如此則正常輸出5次不同的亂數
由上可知:在使用await時,若多個await操作相同變數,并且它們的后續操作是在所有await都回傳后執行,就容易出現與預期不符的情況,應盡量避免,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/936.html
標籤:JavaScript
