簡述非同步編程&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();
新手一枚,大家有啥好的想法或者問題歡迎一起討論