1. 程式人生 > >簡述非同步程式設計&Promise&非同步函式

簡述非同步程式設計&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