前端异步解决方案大全

紙飛機 2021-09-19 14:24:26 阅读数:519

前端 异步 解决方案 解决 方案

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

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

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

同步模式:就是上面所說的一種執行模式,後一個任務等待前一個任務結束,然後再執行,程序的執行順序與任務的排列順序是一致的、同步的。

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

任務執行圖示

“异步模式”非常重要。在瀏覽器端,耗時很長的操作都應該异步執行,避免瀏覽器失去響應,最好的例子就是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個:

<ul><li>Promise</li><li>gengerator函數</li><li>async await </li><li>node.js中 nextTick setImmidate</li><li>第三方庫 async.js</li></ul>

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

1.Promise(重點)

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

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

<p class="has-text-align-left has-small-font-size">發展:Promise 是异步編程的一種解决方案,比傳統的解决方案–回調函數和事件--更合理和更强大。它由社區最早提出和實現,ES6將其寫進了語言標准,統一了語法,原生提供了Promise

(2).它的一般形式:

new Promise(
/* executor */
function(resolve, reject) {
if (/* success */) {
// ...執行代碼
resolve();
} else { /* fail */
// ...執行代碼
reject();
}
}
);

其中,Promise中的參數executor是一個執行器函數,它有兩個參數resolvereject。它內部通常有一些异步操作,如果异步操作成功,則可以調用resolve()來將該實例的狀態置為fulfilled,即已完成的,如果一旦失敗,可以調用reject()來將該實例的狀態置為rejected,即失敗的。

我們可以把Promise對象看成是一條工廠的流水線,對於流水線來說,從它的工作職能上看,它只有三種狀態,一個是初始狀態(剛開機的時候),一個是加工產品成功,一個是加工產品失敗(出現了某些故障)。同樣對於Promise對象來說,它也有三種狀態:pending: 初始狀態,也稱為未定狀態,就是初始化Promise時,調用executor執行器函數後的狀態。 fulfilled:完成狀態,意味著异步操作成功。

<ul><li>pending:初始狀態,也稱為未定狀態,就是初始化Promise時,調用executor執行器函數後的狀態。</li><li>fulfilled:完成狀態,意味著异步操作成功。</li><li>rejected:失敗狀態,意味著异步操作失敗。</li></ul>

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

<ul><li>操作成功:pending -> fulfilled</li><li>操作失敗:pending -> rejected</li></ul>

<p style="font-size:14px" class="has-text-color has-vivid-red-color">注意:並且這個狀態轉化是單向的,不可逆轉,已經確定的狀態(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將會變成拒絕狀態。

  1. 如果then()方法調用resolve()方法,那麼返回的Promise將會變成接收狀態。
  2. 如果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 &lt; 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() {
});
}

<p class="has-text-color has-small-font-size has-vivid-red-color">上述代碼中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數據傳入

在這裏插入圖片描述

注意代碼的第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提取出來了

  1. 通過Promise + async,我們可以把多層函數嵌套(异步執行)的裏層函數得到的數據 返回出來

<p style="font-size:14px">關於async/await總結

放在一個函數前的async有兩個作用:

<ul><li>使函數總是返回一個promise</li><li>允許在這其中使用await</li></ul>

promise前面的await關鍵字能够使JavaScript等待,直到promise處理結束。然後:

<ul><li>如果它是一個錯誤,异常就產生了,就像在那個地方調用了throw error一樣。</li><li>否則,它會返回一個結果,我們可以將它分配給一個值</li></ul>

他們一起提供了一個很好的框架來編寫易於讀寫的异步代碼。

有了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)
})
版权声明:本文为[紙飛機]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210919142426175l.html