您當前的位置:首頁 > 書法

前端非同步(async)解決方案

作者:由 紙飛機 發表于 書法時間:2020-08-01

注:篇幅詳細,請逐步消化

javascript是一門單執行緒語言,即一次只能完成一個任務,若有多個任務要執行,則必須排隊按照佇列來執行(前一個任務完成,再執行下一個任務)。

這種模式執行簡單,但隨著日後的需求,事務,請求增多,這種單執行緒模式執行效率必定低下。只要有一個任務執行消耗了很長時間,在這個時間裡後面的任務無法執行。常見的瀏覽器無響應(假死),往往就是因為某一段Javascript程式碼長時間執行(比如死迴圈),導致整個頁面卡在這個地方,其他任務無法執行。(

弊端

為了解決這個問題,javascript語言將任務執行模式分成同步和非同步:

同步模式:

就是上面所說的一種執行模式

,

後一個任務等待前一個任務結束,然後再執行,程式的執行順序與任務的排列順序是一致的、同步的。

非同步模式

:就是每一個任務有一個或多個回撥函式(callback),前一個任務結束後,不是執行後一個任務,而是執行回撥函式,後一個任務則是不等前一個任務結束就執行,所以程式的執行順序與任務的排列順序是不一致的、非同步的。

前端非同步(async)解決方案

“非同步模式”非常重要。在瀏覽器端,耗時很長的操作都應該非同步執行,避免瀏覽器失去響應,最好的例子就是Ajax操作。在伺服器端,”非同步模式”甚至是唯一的模式,因為執行環境是單執行緒的,如果允許同步執行所有http請求,伺服器效能會急劇下降,很快就會失去響應。(

非同步模式的重要性

下面就帶來幾種前端非同步解決方案:

一。傳統方案

1.回撥函式(callback):

非同步程式設計的基本方法。

首先需要宣告,回撥函式只是一種實現,並不是非同步模式特有的實現。回撥函式同樣可以運用到同步(阻塞)的場景下以及其他一些場景。

回撥函式的定義:

函式A作為引數(函式引用)傳遞到另一個函式B中,並且這個函式B執行函式A。我們就說函式A叫做回撥函式。如果沒有名稱(函式表示式),就叫做匿名回撥函式。

生活舉例:約會結束後你送你女朋友回家,離別時,你肯定會說:“到家了給我發條資訊,我很擔心你。” 然後你女朋友回家以後還真給你發了條資訊。其實這就是一個回撥的過程。你留了個引數函式(要求女朋友給你發條資訊)給你女朋友,然後你女朋友回家,回家的動作是主函式。她必須先回到家以後,主函式執行完了,再執行傳進去的函式,然後你就收到一條資訊了。

案例:

//定義主函式,回撥函式作為引數

function

A

callback

{

callback

();

console

log

‘我是主函式’

);

}

//定義回撥函式

function

B

(){

setTimeout

“console。log(‘我是回撥函式’)”

3000

);

//模仿耗時操作

}

//呼叫主函式,將函式B傳進去

A

B

);

//輸出結果

我是主函式

我是回撥函式

上面的程式碼中,我們先定義了主函式和回撥函式,然後再去呼叫主函式,將回調函式傳進去。

定義主函式的時候,我們讓程式碼先去執行callback()回撥函式,但輸出結果卻是後輸出回撥函式的內容。這就說明了主函式不用等待回撥函式執行完,可以接著執行自己的程式碼。所以一般回撥函式都用在耗時操作上面。比如ajax請求,比如處理檔案等。

優點:

簡單,容易理解和 部署。

缺點:

不利於程式碼的閱讀,和維護,各部分之間高度耦合,流程會很混亂,而且每一個任務只能指定一個回撥函式。

2.事件監聽

採用事件驅動模式。

任務的執行不取決程式碼的順序,而取決於某一個事件是否發生。

監聽函式有:on,bind,listen,addEventListener,observe

以f1和f2為例。首先,為f1繫結一個事件(採用jquery寫法)。

f1

on

‘done’

f2

);

上面程式碼意思是,當f1發生done事件,就執行f2。

然後對f1進行改寫:

function

f1

(){

settimeout

function

(){

//f1的任務程式碼

f1

trigger

‘done’

);

},

1000

);

}

f1。trigger(‘done’)表示,執行完成後,立即觸發done事件,從而開始執行f2。

優點:

比較容易理解,可以繫結多個事件,每一個事件可以指定多個回撥函式,而且可以去耦合,有利於實現模組化。

缺點:

整個程式都要變成事件驅動型,執行流程會變得不清晰。

事件鑑定方法:

(1).onclick方法:

element

onclick

=

function

(){

//處理函式

}

優點

:寫法相容到主流瀏覽器。

缺點

:當同一個

element元素

繫結多個事件時,只有最後一個事件會被新增。

例如:

element

onclick

=

handler1

element

onclick

=

handler2

element

onclick

=

handler3

上訴只有handler3會被新增執行,所以我們使用另外一種方法新增事件。(2)attachEvent和addEvenListener方法

(2).attachEvent和addEvenListener方法:

//IE:attachEvent(IE下的事件監聽)

elment

attachEvent

“onclick”

handler1

);

elment

attachEvent

“onclick”

handler2

);

elment

attachEvent

“onclick”

handler3

);

上述三個方法執行順序:3-2-1;

//標準addEventListener(標準下的監聽)

elment

addEvenListener

“click”

handler1

false

);

elment

addEvenListener

“click”

handler2

false

);

elment

addEvenListener

“click”

handler3

false

);

>

執行順序:1-2-3;

PS:該方法的第三個引數是冒泡獲取(useCapture),是一個布林值:當為false時表示由裡向外(事件冒泡),true表示由外向裡(事件捕獲)。

<

div

id

=

“id1”

>

<

div

id

=

“id2”

><

/div>

<

/div>

document

getElementById

“id1”

)。

addEventListener

“click”

function

(){

console

log

‘id1’

);

},

false

);

document

getElementById

“id2”

)。

addEventListener

“click”

function

({

console

log

‘id2’

);

},

false

);

//點選id=id2的div,先在console中輸出,先輸出id2,在輸出id1

document

getElementById

“id1”

)。

addEventListener

“click”

function

({

console

log

‘id1’

);

},

false

);

document

getElementById

“id2”

)。

addEventListener

“click”

function

({

console

log

‘id2’

);

},

true

);

//點選id=id2的div,先在console中輸出,先輸出id1,在輸出id2

(3).DOM方法addEventListener()和removeListenner():

addEventListenner()和removeListenner()表示用來分配和刪除事件的函式。這兩種方法都需要三種引數,分別為:string(事件名稱),要觸發事件的函式function,指定事件的處理函式的時期或者階段(boolean)。

例子見(2)

(4).通用的時間新增方法:

on

function

elment

type

handler

){

//新增事件

return

element

attachEvent

elment

attachEvent

“on”

+

type

handler

elment

addEventListener

type

handler

false

);

}

事件冒泡和事件捕獲的區別,可以參考:

二。工具方案

工具方案大致分為以下5個:

Promise

gengerator函式

async await

node。js中 nextTick setImmidate

第三方庫 async。js

下面針對每一個做詳細說明應用:

1.Promise(重點)

(1).Promise的含義和發展:

含義:Promise 物件用於一個非同步操作的最終完成(或失敗)及其結果值的表示。簡單點說,它就是用於處理非同步操作的,非同步處理成功了就執行成功的操作,非同步處理失敗了就捕獲錯誤或者停止後續操作。

發展:Promise 是非同步程式設計的一種解決方案,比傳統的解決方案–回撥函式和事件--更合理和更強大。它由社群最早提出和實現,ES6將其寫進了語言標準,統一了語法,原生提供了Promise

(2).它的一般形式:

new Promise(

/* executor */

function(resolve, reject) {

if (/* success */) {

// 。。。執行程式碼

resolve();

} else { /* fail */

// 。。。執行程式碼

reject();

}

}

);

其中,Promise中的引數executor是一個

執行器函式

,它有兩個引數

resolve

reject

。它內部通常有一些非同步操作,如果非同步操作成功,則可以呼叫resolve()來將該例項的狀態置為

fulfilled

,即已完成的,如果一旦失敗,可以呼叫reject()來將該例項的狀態置為

rejected

,即失敗的。

我們可以把Promise物件看成是一條工廠的流水線,對於流水線來說,從它的工作職能上看,它只有三種狀態,一個是初始狀態(剛開機的時候),一個是加工產品成功,一個是加工產品失敗(出現了某些故障)。同樣對於Promise物件來說,它也有三種狀態:

pending:

初始狀態,也稱為未定狀態,就是初始化Promise時,呼叫executor執行器函式後的狀態。

fulfilled:

完成狀態,意味著非同步操作成功。

pending:

初始狀態,也稱為未定狀態,就是初始化Promise時,呼叫executor執行器函式後的狀態。

fulfilled:

完成狀態,意味著非同步操作成功。

rejected:

失敗狀態,意味著非同步操作失敗。

它只有兩種狀態可以轉化,即

操作成功:

pending -> fulfilled

操作失敗:

pending -> rejected

注意:並且這個狀態轉化是單向的,不可逆轉,已經確定的狀態(fulfilled/rejected)無法轉回初始狀態(pending)。

(3).Promise物件的方法(api):

1):Promise.prototype.then(callback)

Promise物件含有

then方法

,then()呼叫後返回一個Promise物件,意味著例項化後的Promise物件可以進行鏈式呼叫,而且這個then()方法可以接收兩個函式,一個是處理成功後的函式,一個是處理錯誤結果的函式。

如下:

var

promise1

=

new

Promise

function

resolve

reject

{

// 2秒後置為接收狀態

setTimeout

function

()

{

resolve

‘success’

);

},

2000

);

});

promise1

then

function

data

{

console

log

data

);

// success

},

function

err

{

console

log

err

);

// 不執行

})。

then

function

data

{

// 上一步的then()方法沒有返回值

console

log

‘鏈式呼叫:’

+

data

);

// 鏈式呼叫:undefined

})。

then

function

data

{

// 。。。。

});

在這裡我們主要關注promise1。then()方法呼叫後返回的Promise物件的狀態,是pending還是fulfilled,或者是rejected?

返回的這個Promise物件的狀態主要是根據promise1。then()方法返回的值,大致分為以下幾種情況:

1。如果then()方法中返回了一個引數值,那麼返回的Promise將會變成接收狀態。

2。如果then()方法中丟擲了一個異常,那麼返回的Promise將會變成拒絕狀態。

3。 如果then()方法呼叫resolve()方法,那麼返回的Promise將會變成接收狀態。

4。 如果then()方法呼叫reject()方法,那麼返回的Promise將會變成拒絕狀態。

5。如果then()方法返回了一個未知狀態(pending)的Promise新例項,那麼返回的新Promise就是未知 狀態。

6。如果then()方法沒有明確指定的resolve(data)/reject(data)/return data時,那麼返回的新Promise就是接收狀態,可以一層一層地往下傳遞。

2):Promise.prototype.catch(callback)

catch()方法和then()方法一樣,都會返回一個新的Promise物件,它主要用於捕獲非同步操作時出現的異常。因此,我們通常省略then()方法的第二個引數,把錯誤處理控制權轉交給其後面的catch()函式,如下:

var

promise3

=

new

Promise

function

resolve

reject

{

setTimeout

function

()

{

reject

‘reject’

);

},

2000

);

});

promise3

then

function

data

{

console

log

‘這裡是fulfilled狀態’

);

// 這裡不會觸發

// 。。。

})。

catch

function

err

{

// 最後的catch()方法可以捕獲在這一條Promise鏈上的異常

console

log

‘出錯:’

+

err

);

// 出錯:reject

});

3):Promise.all()

Promise。all()接收一個引數,它必須是可以迭代的,比如陣列。

它通常用來處理一些併發的非同步操作,即它們的結果互不干擾,但是又需要非同步執行。它最終只有兩種狀態:成功或者失敗。

指的是將陣列中所有的任務執行完成之後, 才執行。then 中的任務

它的狀態受引數內各個值的狀態影響,即裡面狀態全部為fulfilled時,它才會變成fulfilled,否則變成rejected。

成功呼叫後返回一個數組,陣列的值是有序的,即按照傳入引數的陣列的值操作後返回的結果。

如下:

const

p1

=

new

Promise

((

resolve

reject

)=>{

setTimeout

(()=>{

resolve

console

log

‘p1 任務1’

))

},

1000

})

then

data

=>

{

console

log

‘p1 任務2’

})

then

res

=>

{

console

log

‘p1 任務3’

})

catch

err

=>{

throw

err

}

const

p2

=

new

Promise

((

resolve

reject

)=>{

resolve

console

log

‘p2 任務1’

))

})。

then

data

=>

{

console

log

‘p2 任務2’

}

)。

catch

err

=>

{

throw

err

}

//只有在p1,p2都執行完後才會執行then裡的內容

Promise

all

([

p1

p2

])

then

(()=>

console

log

‘done’

))

4):Promise.race()

Promise。race()和Promise。all()類似,都接收一個可以迭代的引數,但是不同之處是Promise。race()的狀態變化不是全部受引數內的狀態影響,

一旦引數內有一個值的狀態發生的改變,那麼該Promise的狀態就是改變的狀態。就跟race單詞的字面意思一樣,誰跑的快誰贏

。如下:

var

p1

=

new

Promise

function

resolve

reject

{

setTimeout

resolve

300

‘p1 doned’

);

});

var

p2

=

new

Promise

function

resolve

reject

{

setTimeout

resolve

50

‘p2 doned’

);

});

var

p3

=

new

Promise

function

resolve

reject

{

setTimeout

reject

100

‘p3 rejected’

);

});

Promise

race

([

p1

p2

p3

])。

then

function

data

{

// 顯然p2更快,所以狀態變成了fulfilled

// 如果p3更快,那麼狀態就會變成rejected

console

log

data

);

// p2 doned

})。

catch

function

err

{

console

log

err

);

// 不執行

});

5):Promise.resolve()

Promise。resolve()接受一個引數值,可以是普通的值,具有then()方法的物件和Promise例項。正常情況下,它返回一個Promise物件,狀態為fulfilled。但是,當解析時發生錯誤時,返回的Promise物件將會置為rejected態。如下:

// 引數為普通值

var

p4

=

Promise

resolve

5

);

p4

then

function

data

{

console

log

data

);

// 5

});

// 引數為含有then()方法的物件

var

obj

=

{

then

function

()

{

console

log

‘obj 裡面的then()方法’

);

}

};

var

p5

=

Promise

resolve

obj

);

p5

then

function

data

{

// 這裡的值時obj方法裡面返回的值

console

log

data

);

// obj 裡面的then()方法

});

// 引數為Promise例項

var

p6

=

Promise

resolve

7

);

var

p7

=

Promise

resolve

p6

);

p7

then

function

data

{

// 這裡的值時Promise例項返回的值

console

log

data

);

// 7

});

// 引數為Promise例項,但引數是rejected態

var

p8

=

Promise

reject

8

);

var

p9

=

Promise

resolve

p8

);

p9

then

function

data

{

// 這裡的值時Promise例項返回的值

console

log

‘fulfilled:’

+

data

);

// 不執行

})。

catch

function

err

{

console

log

‘rejected:’

+

err

);

// rejected: 8

});

6):Promise.reject()

Promise。reject()和Promise。resolve()正好相反,它接收一個引數值reason,即發生異常的原因。此時返回的Promise物件將會置為rejected態。如下:

var

p10

=

Promise

reject

‘手動拒絕’

);

p10

then

function

data

{

console

log

data

);

// 這裡不會執行,因為是rejected態

})。

catch

function

err

{

console

log

err

);

// 手動拒絕

})。

then

function

data

{

// 不受上一級影響

console

log

‘狀態:fulfilled’

);

// 狀態:fulfilled

});

總之,除非Promise。then()方法內部丟擲異常或者是明確置為rejected態,否則它返回的Promise的狀態都是fulfilled態,即完成態,並且它的狀態不受它的上一級的狀態的影響。

2.gengerator函式

在非同步程式設計中,還有一種常用的解決方案,它就是Generator

生成器函式

。顧名思義,它是一個生成器,它也是一個狀態機,內部擁有值及相關的狀態,生成器返回一個迭代器Iterator物件,我們可以透過這個迭代器,手動地遍歷相關的值、狀態,保證正確的執行順序。

es6 提供的

generator函式

總得來說就三點:

*在function關鍵字後加一個* , 那麼這個函式就稱之為generator函式

*函式體有關鍵字 yield , 後面跟每一個任務 , 也可以有return關鍵字, 保留一個數據

*透過

next函式

呼叫, 幾個呼叫, 就是幾個人任務執行

(1).簡單使用

Generator的宣告方式類似一般的函式宣告,只是多了個*號,並且一般可以在函式內看到yield關鍵字

function

*

showWords

()

{

yield

‘one’

yield

‘two’

return

‘three’

}

var

show

=

showWords

();

show

next

()

// {done: false, value: “one”}

show

next

()

// {done: false, value: “two”}

show

next

()

// {done: true, value: “three”}

show

next

()

// {value: underfined, done: true}

如上程式碼,定義了一個showWords的生成器函式,呼叫之後返回了一個迭代器物件(即show)

呼叫next方法後,函式內執行第一條yield語句,輸出當前的狀態done(迭代器是否遍歷完成)以及相應值(一般為yield關鍵字後面的運算結果)

每呼叫一次next,則執行一次yield語句,並在該處暫停,return完成之後,就退出了生成器函式,後續如果還有yield操作就不再執行了

當然還有以下情況:(next()數量小於yield)

function

*

g1

(){

yield

‘任務1’

yield

‘任務2’

yield

‘任務3’

return

‘任務4’

}

const

g1done

=

g1

()

console

log

g1done

next

())

//{ value: ‘任務1’, done: false }

console

log

g1done

next

())

//{ value: ‘任務2’, done: false }

(2).yield和yield*

有時候,我們會看到yield之後跟了一個*號,它是什麼,有什麼用呢?

類似於生成器前面的*號,yield後面的星號也跟生成器有關,舉個大栗子:

function

*

showWords

()

{

yield

‘one’

yield

showNumbers

();

return

‘three’

}

function

*

showNumbers

()

{

yield

10

+

1

yield

12

}

var

show

=

showWords

();

show

next

()

// {done: false, value: “one”}

show

next

()

// {done: false, value: showNumbers}

show

next

()

// {done: true, value: “three”}

show

next

()

// {done: true, value: undefined}

增添了一個生成器函式,我們想在showWords中呼叫一次,簡單的 yield showNumbers()之後發現並沒有執行函數里面的yield 10+1

因為yield只能原封不動地返回右邊運算後值,但現在的showNumbers()不是一般的函式呼叫,返回的是迭代器物件

所以換個yield* 讓它自動遍歷進該物件

function

*

showWords

()

{

yield

‘one’

yield

*

showNumbers

();

return

‘three’

}

function

*

showNumbers

()

{

yield

10

+

1

yield

12

}

var

show

=

showWords

();

show

next

()

// {done: false, value: “one”}

show

next

()

// {done: false, value: 11}

show

next

()

// {done: false, value: 12}

show

next

()

// {done: true, value: “three”}

要注意的是,這yield和yield* 只能在generator函式內部使用,一般的函式內使用會報錯

function

showWords

()

{

yield

‘one’

// Uncaught SyntaxError: Unexpected string

}

雖然換成yield*不會直接報錯,但使用的時候還是會有問題,因為’one‘字串中沒有Iterator介面,沒有yield提供遍歷

function

showWords

()

{

yield

*

’one‘

}

var

show

=

showWords

();

show

next

()

// Uncaught ReferenceError: yield is not defined

在爬蟲開發中,我們常常需要請求多個地址,為了保證順序,引入Promise物件和Generator生成器函式,看這個簡單的栗子:

var

urls

=

’url1‘

’url2‘

’url3‘

];

function

*

request

urls

{

urls

forEach

function

url

{

yield

req

url

);

});

// for (var i = 0, j = urls。length; i <; j; ++i) {

// yield req(urls[i]);

// }

}

var

r

=

request

urls

);

r

next

();

function

req

url

{

var

p

=

new

Promise

function

resolve

reject

{

$

get

url

function

rs

{

resolve

rs

);

});

});

p

then

function

()

{

r

next

();

})。

catch

function

()

{

});

}

上述程式碼中forEach遍歷url陣列,匿名函式內部不能使用yield關鍵字,改換成註釋中的for迴圈就行了

(3).next()呼叫中的傳參

引數值有注入的功能,可改變上一個yield的返回值,如

function

*

showNumbers

()

{

var

one

=

yield

1

var

two

=

yield

2

*

one

yield

3

*

two

}

var

show

=

showNumbers

();

show

next

()。

value

// 1

show

next

()。

value

// NaN

show

next

2

)。

value

// 6

第一次呼叫next之後返回值one為1,但在第二次呼叫next的時候one其實是undefined的,因為generator不會自動儲存相應變數值,我們需要手動的指定,這時two值為NaN,在第三次呼叫next的時候執行到yield 3 * two,透過傳參將上次yield返回值two設為2,得到結果

另一個栗子:

由於ajax請求涉及到網路,不好處理,這裡用了setTimeout模擬ajax的請求返回,按順序進行,並傳遞每次返回的資料

var

urls

=

’url1‘

’url2‘

’url3‘

];

function

*

request

urls

{

var

data

for

var

i

=

0

j

=

urls

length

i

&

lt

j

++

i

{

data

=

yield

req

urls

i

],

data

);

}

}

var

r

=

request

urls

);

r

next

();

function

log

url

data

cb

{

setTimeout

function

()

{

cb

url

);

},

1000

);

}

function

req

url

data

{

var

p

=

new

Promise

function

resolve

reject

{

log

url

data

function

rs

{

if

rs

{

reject

();

}

else

{

resolve

rs

);

}

});

});

p

then

function

data

{

console

log

data

);

r

next

data

);

})。

catch

function

()

{

});

}

達到了按順序請求三個地址的效果,初始直接r。next()無引數,後續透過r。next(data)將data資料傳入

前端非同步(async)解決方案

注意程式碼的第16行,這裡引數用了url變數,是為了和data資料做對比

因為初始next()沒有引數,若是直接將url換成data的話,就會因為promise物件的資料判斷 !rs == undefined 而reject

所以將第16行換成 cb(data || url);

透過模擬的ajax輸出,可瞭解到next的傳參值,第一次在log輸出的是 url = ’url1‘值,後續將data = ’url1‘傳入req請求,在log中輸出 data = ’url1‘值

(4).for...of迴圈代替.next()

除了使用。next()方法遍歷迭代器物件外,透過ES6提供的新迴圈方式for…of也可遍歷,但與next不同的是,它會忽略return返回的值,如

function

*

showNumbers

()

{

yield

1

yield

2

return

3

}

var

show

=

showNumbers

();

for

var

n

of

show

{

console

log

n

// 1 2

}

此外,處理for…of迴圈,具有呼叫迭代器介面的方法方式也可遍歷生成器函式,如擴充套件運算子…的使用

function

*

showNumbers

()

{

yield

1

yield

2

return

3

}

var

show

=

showNumbers

();

[。。。

show

// [1, 2, length: 2]

更多使用可以參考:MDN - Generator

3.async await (重點)

es7新增的

async函式

可以更舒適地與promise協同工作,它叫做async/await,它是非常的容易理解和使用。

(1)。格式

async

function

aa

(){

await

’任務1‘

await

’任務2‘

}

async:

讓我們先從

async關鍵字

說起,它被放置在一個函式前面。就像下面這樣:

async

function

timeout

()

{

return

’hello world‘

}

函式前面的async一詞意味著一個簡單的事情:這個函式總是返回一個promise,如果程式碼中有return <非promise>語句,JavaScript會自動把返回的這個value值包裝成promise的resolved值。

例如,上面的程式碼返回resolved值為1的promise,我們可以測試一下:

async

function

f

()

{

return

1

}

f

()。

then

alert

// 彈出1

我們也可以顯式的返回一個promise,這個將會是同樣的結果

async

function

f

()

{

return

Promise

resolve

1

}

f

()。

then

alert

// 彈出1

所以,async確保了函式返回一個promise,即使其中包含非promise,這樣都不需要你來書寫繁雜的Promise,夠簡單了吧?但是不僅僅只是如此,還有另一個關鍵詞

await

,只能在async函數里使用,同樣,它也很cool。

await:

// 只能在async函式內部使用

let

value

=

await

promise

關鍵詞await可以讓JavaScript進行等待,直到一個promise執行並返回它的結果,JavaScript才會繼續往下執行。

以下是一個promise在1s之後resolve的例子:

async

function

f

()

{

let

promise

=

new

Promise

((

resolve

reject

=>

{

setTimeout

(()

=>

resolve

’done!‘

),

1000

})

let

result

=

await

promise

// 直到promise返回一個resolve值(*)

alert

result

// ’done!‘

}

f

()

函式執行到(await)行會‘暫停’,不再往下執行,

當promise處理完成後重新恢復執行, resolve的值成了最終的result,所以上面的程式碼會在1s後輸出’done!‘

我們強調一下:await字面上使得JavaScript等待,直到promise處理完成,

然後將結果繼續下去。這並不會花費任何的cpu資源,因為引擎能夠同時做其他工作:執行其他指令碼,處理事件等等。

這只是一個更優雅的得到promise值的語句,它比promise更加容易閱讀和書寫。

注意不:能在常規函數里使用await

如果我們試圖在非async函數里使用await,就會出現一個語法錯誤:

function

f

()

{

let

promise

=

Promise

resolve

1

let

result

=

await

promise

// syntax error

}

//Uncaught SyntaxError: await is only valid in async function

如果我們忘記了在函式之前放置async,我們就會得到這樣一個錯誤。如上所述,await只能在async函式中工作。

就以前面幾個案例可能還看不出async/await 的作用,如果我們要計算3個數的值,然後把得到的值進行輸出呢?

async

function

testResult

()

{

let

first

=

await

doubleAfter2seconds

30

);

let

second

=

await

doubleAfter2seconds

50

);

let

third

=

await

doubleAfter2seconds

30

);

console

log

first

+

second

+

third

);

}

6秒後,控制檯輸出220, 我們可以看到,寫非同步程式碼就像寫同步程式碼一樣了,再也沒有回撥地域了。

再來一個看看:先來個問題

readFile(’。/01-Promise。js‘) 執行結果是Promise, 但是我們使用 async await之後, 它的結果是具體的資料了?

用到了Node。js裡的fs模組,fs模組是檔案模組,可以操作檔案,readFile()是讀一個檔案,不瞭解的樂意看Node。js官方文件

const

fs

=

require

’fs‘

//匯入fs模組

const

readFile

=

filename

=>{

return

new

Promise

((

resolve

reject

)=>{

fs

readFile

filename

,(

err

data

)=>{

resolve

data

toString

())

})

})

}

const

asyncFn

=

async

()

=>

{

//const f0 = eadFile(’。/01-Promise。js‘) //類似{value: ’檔案內容‘, done: false}

const

f1

=

await

readFile

’。/01-Promise。js‘

//檔案內容

//const f1 = readFile(’。/01-Promise。js‘)。then(data=>data)

const

f2

=

await

readFile

’。/02-generator。js‘

//檔案內容

console

log

f1

console

log

f2

}

asyncFn

()

readFile()定義了一個Promise方法讀取檔案,這裡有個坑,我們現在是在裡面返回出資料了的,要知道這裡面有3層函式,如果不用new Promise這個方法,大家可以試試用常規方法能不能返回資料,先透個底拿不到,大家可以試試。

asyncFn()輸出了檔案內容,在

const f1 = eadFile('./01-Promise.js')

這一句這一句會打印出出一個Promise{’檔案內容‘},有點類似前面的generator函式輸出的{value: ’‘, done: false},只不過省略了done,大家知道,我們讀檔案,肯定是要裡面的內容的,如果輸出 Promise{’檔案內容‘} ,我們是不好取出內容的,但是await很好的幫我們解決了這個問題,前面加上await直接輸出了檔案內容。

所以:這個問題可以有個小總結

1。async函式使用了generator函式的語法糖 , 它直接生成物件 {value: ’‘,done:false} await 直接將value提取出來了

2。 透過Promise + async,我們可以把多層函式巢狀(非同步執行)的裡層函式得到的資料 返回出來

關於async/await總結

放在一個函式前的

async

有兩個作用:

使函式總是返回一個promise

允許在這其中使用await

promise前面的

await

關鍵字能夠使JavaScript等待,直到promise處理結束。然後:

如果它是一個錯誤,異常就產生了,就像在那個地方呼叫了throw error一樣。

否則,它會返回一個結果,我們可以將它分配給一個值

他們一起提供了一個很好的框架來編寫易於讀寫的非同步程式碼。

有了async/await,我們很少需要寫promise。then/catch,但是我們仍然不應該忘記它們是基於promise的,因為有些時候(例如在最外面的範圍內)我們不得不使用這些方法。Promise。all也是一個非常棒的東西,它能夠同時等待很多工。

4.node.js nextTick setImmidate

nextTick vs setImmediate

輪詢:

nodejs中是事件驅動的,有一個迴圈執行緒一直從

事件佇列

中取任務執行或者

I/O的操作轉給後臺執行緒池來操作,把這個迴圈執行緒的每次執行的過程算是一次輪詢。

2。setImmediate()的使用

即時計時器立即執行工作,它是在事件輪詢之後執行,為了防止輪詢阻塞,每次只會呼叫一個。

3。Process。nextTick()的使用

它和setImmediate()執行的順序不一樣,它是在事件輪詢之前執行,為了防止I/O飢餓,所以有一個預設process。maxTickDepth=1000來限制事件佇列的每次迴圈可執行的nextTick()事件的數目。

總結:

nextTick()的回撥函式執行的優先順序要高於setImmediate();

process。nextTick()屬於idle觀察者,setImmediate()屬於check觀察者。在每一輪迴圈檢查中,idle觀察者先於I/O觀察者,I/O觀察者先於check觀察者。

在具體實現上,process。nextTick()的回撥函式儲存在一個數組中,

setImmediate()的結果則是儲存在連結串列中。

在行為上,process。nextTick()在每輪迴圈中會將陣列中的回撥函式全部執行完。

而setImmediate()在每輪迴圈中執行連結串列中的一個回撥函式。

5.第三方庫 async.js

async.js

是一個第三方庫,帶有很多api

暴露了一個async物件,這個物件身上有很多的api(多工執行),例如parallel,series

async

parallel

&

#

91

function

callback

){

callback

null

’任務1‘

},

function

callback

){

callback

null

’任務2‘

},

],(

err

data

)=>{

console

log

’data‘

data

})

標簽: promise  function  函式  data  log