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

 



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM