簡述非同步程式設計&Promise&非同步函式
前言:文章由本人在學習之餘總結鞏固思路,不足之前還請指出。
一.非同步程式設計
首先我們先簡單來回顧一下同步API和非同步API的概念
1.同步API:只有當前的API執行完成之前,才會執行下一個API
例:
console.log(‘first'); console.log('last); 結果: first last
2.非同步API:當前API的執行不會阻塞後續程式碼的執行
例:
console.log('first'); setTimeout( () => { console.log('last'); }, 2000); console.log('middle'); 結果: first middle last
執行順序分析:
首先指令碼會先執行同步程式碼,這時有一個同步程式碼區,按著從上到下的順序進行。當所有的同步程式碼執行完成之後,再進入非同步程式碼區查詢是否有非同步程式碼,並開始執行,執行完之後,呼叫對應的回撥函式。
拿例子中的計時器來說,該非同步函式每2秒都會重新呼叫一次。
注意:即使是計時器的時間設定為0,它任是一個非同步API,不會隨著同步一起執行。
3.Node.js中的非同步API
fs.readFile('./demo.txt', (err, result) => {});
當硬碟讀取了檔案之後,呼叫後方的回撥函式;
但這個時候,我們有了一個需求,依次讀取A,B,C三個檔案。由於檔案大小不一定是我們知道的,所以讀取,查詢的速度我們也並不知道。
按照同步的思路來寫的話:
fs.readFile('./1.txt', 'utf8', (err, result1) => {console.log(result1)}); fs.readFile('./2.txt', 'utf8', (err, result2) => {console.log(result2)}); fs.readFile('./3.txt', 'utf8', (err, result3) => {console.log(result3)});
結果未必如我們所願。
這時我們也許會想到一個辦法,既然該API是非同步API,我們把各個API巢狀起來,這樣不久可以依次執行了?
fs.readFile('./1.txt', 'utf8', (err, result1) => { console.log(result1) fs.readFile('./2.txt', 'utf8', (err, result2) => { console.log(result2) fs.readFile('./3.txt', 'utf8', (err, result3) => { console.log(result3) }) }) });
emmm....確實可以,但他的缺點也能預料到:這裡只有三個檔案,可能不易看出其劣勢,但是如果是300個呢?程式碼的可讀性將大幅度降低,維護的難度相應提高,這是我們不願意看到的。
這一現象,我們稱其為回撥地獄(callbackhell)。進來了就si裡面了。
解決的辦法當然也有,這時該輪到我們的Promise登場了。
二.Promise
Promise出現的目的是解決Node.js非同步程式設計中回撥地獄的問,它是一個建構函式,我們要用new Promise的方法呼叫。
我們先來簡單地介紹一下Promise。
let promise = new Promise((resolve, reject) => {});
Promise的引數為一個匿名回撥函式,其中reslove,reject也是回撥函式,他能將非同步API的執行和結果進行分離,reslove對應著result(正常思路下)當fs有返回結果的時候,我們可以將其通過回撥函式的方式將其傳送到外面,
同理,當fs出現錯誤的時候,我們可以將其傳送到外面進行處理。這裡要用到Promise下面的兩個方法promise.then()&promise.catch(),分別用來對結果和錯誤資訊進行處理。
我們結合例項來分析這些回撥函式。
let promise = new Promise((resolve, reject) => { fs.readFile('./1.txt', 'utf8', (err, result) => { if (err != null) { reject(err);
相當於執行then裡面的回撥函式 }else { resolve(result);
相當於執行catch裡面的回撥函式 } }); });
promise.then((result) => {
console.log(result);
})
.catch((err)=> {
console.log(err);
})
用此方法來對我們之前的函式進行包裝,分析一下,既然我們有三個非同步API,我們則需要用三個Promise將他們包裹起來,我們需要讓這三個promise依次執行,但是如果我們直接聲明瞭一個變數等於promise的話就直接執行了,所以
這裡我們用一個函式把他封裝起來
function p1 () { return new Promise ((resolve, reject) => { fs.readFile('./1.txt', 'utf8', (err, result) => { resolve(result) }) }); } function p2 () { return new Promise ((resolve, reject) => { fs.readFile('./2.txt', 'utf8', (err, result) => { resolve(result) }) }); } function p3 () { return new Promise ((resolve, reject) => { fs.readFile('./3.txt', 'utf8', (err, result) => { resolve(result) }) }); }
為了實現順序呼叫,我們使用鏈式程式設計
p1().then((r1)=> { console.log(r1); return p2(); }) .then((r2)=> { console.log(r2); return p3(); }) .then((r3) => { console.log(r3) })
注意:這裡return 呼叫返回的結果我們可以參考MDN的解釋
返回一個已經是接受狀態的 Promise,那麼 then
返回的 Promise 也會成為接受狀態,並且將那個 Promise 的接受狀態的回撥函式的引數值作為該被返回的Promise的接受狀態回撥函式的引數值。
這裡看上去我們的程式碼的似乎比之前的巢狀關係更為複雜,這裡就要引入我們的非同步函數了。
3.非同步函式
非同步函式是非同步程式設計語法的終極解決方案,它可以讓我們將非同步程式碼寫成同步的形式,讓程式碼不再有回撥函式巢狀,使程式碼變得清晰明瞭。
舉一個簡單的函式來看看他的用法
// 1.在普通函式定義的前面加上async關鍵字 普通函式就變成了非同步函式 // 2.非同步函式預設的返回值是promise物件 // 3.在非同步函式內部使用throw關鍵字進行錯誤的丟擲 async function fn () {
這裡我們發現我們不需要再new一個Promise再將其返回了 return 123; //正常的時候用return // throw '發生了一些錯誤';出錯的時候throw } console.log(fn ()) fn () .then(function (data) { console.log(data); }) .catch(function (err){ console.log(err); })
那麼如何讓我們的函式有序地進行呢?這裡我們不用再採用return巢狀,接下來就要用到await了
我們定義一個run函式
// await關鍵字 // 1.它只能出現在非同步函式中 // 2.await promise 它可以暫停非同步函式的執行 等待promise物件返回結果後再向下執行函式 async function run () { let r1 = await p1() let r2 = await p2() // await不能直接得到throw並賦值給r3這裡我們採用catch試試 let r3 = p3().catch(n => console.log(n));// p3 console.log(r1) console.log(r2) console.log(r3 instanceof Promise)//ture // console.log(r3) } 這樣就能實現我們的按順序執行了
此處r3的值是我在記筆記的時候發現await並不直接接受reject的Promise,所以做了個輸出的嘗試,隨意看看就好 run();
新手一枚,大家有啥好的想法或者問題歡迎一起討論
&n