首頁 > 軟體

JavaScript非同步程式設計中async函數詳解

2022-11-09 14:01:07

async函數

async函數的返回值為 promise 物件,promise物件的結果由async函數執行的返回值決定。async函數能使得非同步操作變得更加方便,簡而言之就是 Generator 的語法糖。

定義async函數,特點是即便函數內部返回結果不是promise物件,呼叫函數其最後的返回結果依然是promise物件,程式碼如下:

如果返回的結果不是 Promise 物件的情況下:

<script>
    async function fn(){
        // 返回的結果是字串
        // return '123'
        // // 返回的結果是undefined
        // return;
        // 返回的結果是丟擲一個異常
        throw new 'error'
    }
    const result = fn()
    console.log(result);
</script>

如果返回的結果是 Promise 物件時,我們正常使用 then 方法即可,如下:

<script>
    async function fn(){
        return new Promise((resolve,reject)=>{
            // resolve('成功的資料')
            reject('失敗的資料')
        })
    }
    const result = fn()
    // 呼叫 then 方法
    result.then((value)=>{
        console.log(value);
    },(reason)=>{
        console.log(reason); // 列印失敗的資料
    })
</script>

await 表示式

通過上文的對 async 介紹,感覺其功能有點雞肋,其實恰恰不是,而是 async 需要搭配 await 一起使用才能達到語法糖的效果。

await的特點:

await必須寫在 async 函數中

await右側的表示式一般為 promise 物件

await返回的是 promise 成功的值

await的 promise 失敗了,就會丟擲異常,需要通過 try...catch捕獲處理

說白了:await就相當於 then 方法的第一個回撥函數,只返回成功的值,失敗的值需要 try...catch來捕獲。

async函數內部丟擲錯誤,會導致返回的 Promise 物件變為reject狀態。丟擲的錯誤物件會被catch方法回撥函數接收到。

<script>
    const p = new Promise((resolve,reject)=>{
        // resolve('使用者資料')
        reject('使用者載入資料失敗了')
    })
    async function fn(){
        // 為防止promise是失敗的狀態,加上try...catch進行異常捕獲
        try {
            // await 返回的結果就是 promise 返回成功的值
            let result = await p
            console.log(result);
        } catch (error) {
            console.log(error);//因為是失敗的狀態,所以列印:使用者載入資料失敗了
        }
    }
    fn()
</script>

總結:

(1)await命令後面的Promise物件,執行結果可能是rejected,所以最好把await命令放在try...catch程式碼塊中。

(2)如果有多個await命令後面的非同步操作,如果不存在繼發關係,最好讓它們同時觸發。

比如:await Promise.all([a(), b()]),這裡簡單提一下

(3)await命令只能用在async函數之中,如果用在普通函數,就會報錯。

(4)(理解一下async的執行原理) async 函數可以保留執行堆疊,普通函數內部執行一個非同步任務時,如果非同步任務執行結束普通函數可能早就執行完了,非同步任務的上下文環境已經消失了,如果非同步任務報錯,錯誤堆疊將不包括普通函數;而async函數內部的非同步任務執行時,async函數是暫停執行的,所以一旦async函數內部的非同步任務執行報錯,錯誤堆疊將包括async函數。

async使用形式

// 函數宣告
async function foo() {}
// 函數表示式
const foo = async function () {};
// 物件的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }
  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭頭函數
const foo = async () => {};

async讀取檔案

和之前講解的 promise 讀取檔案內容 一樣,我們也可以使用async進行檔案的讀取,程式碼如下:

// 1.引入 fs 模組
const fs = require('fs')
// 2.讀取檔案
function index(){
    return new Promise((resolve,reject)=>{
        fs.readFile('./index.md',(err,data)=>{
            // 如果失敗
            if(err) reject(err)
            // 如果成功
            resolve(data)
        })
    })
}
function index1(){
    return new Promise((resolve,reject)=>{
        fs.readFile('./index1.md',(err,data)=>{
            // 如果失敗
            if(err) reject(err)
            // 如果成功
            resolve(data)
        })
    })
}
function index2(){
    return new Promise((resolve,reject)=>{
        fs.readFile('./index2.md',(err,data)=>{
            // 如果失敗
            if(err) reject(err)
            // 如果成功
            resolve(data)
        })
    })
}
// 3.宣告一個 async 函數
async function fn(){
    let i = await index()
    let i1 = await index1()
    let i2 = await index2()
    console.log(i.toString());
    console.log(i1.toString());
    console.log(i2.toString());
}
fn()

async傳送AJAX請求

和之前講解 promise傳送ajax請求 一樣,我們也可以使用async進行傳送ajax請求,程式碼如下:

<script>
    // 傳送 AJAX請求,返回的結果是 Promise 物件
    function sendAjax(url){
        return new Promise((resolve,reject)=>{
            // 建立物件
            const x = new XMLHttpRequest()
            // 初始化
            x.open('GET',url)
            // 傳送
            x.send()
            // 事件繫結
            x.onreadystatechange = function(){
                if(x.readyState === 4){
                    if(x.status >= 200 && x.status < 300){
                        // 如果響應成功
                        resolve(x.response)
                        // 如果響應失敗
                        reject(x.status)
                    }
                }
            }
        })
    }
    // promise then 方法測試
    // const result = sendAjax("https://ai.baidu.com/").then(value=>{
    //     console.log(value);
    // },reason=>{})
    // async 與 await 測試
    async function fn(){
        // 傳送 AJAX 請求
        let result = await sendAjax("https://ai.baidu.com/")
        console.log(result);
    }
    fn()
</script>

與生成器(Generator)相比

我們發現 async與await之間的關係 和 Generator與yield之間的關係十分類似,不熟悉Generator的朋友可以看一下我之前的文章:生成器講解 ;一比較就發現: async函數就是將 Generator 函數的星號(*)替換成async,將yield替換成await。程式碼比較如下:

<script>
    // Generator 函數
    function * person() {
        console.log('hello world');
        yield '第一分隔線' 
        console.log('hello world 1');
        yield '第二分隔線'
        console.log('hello world 2');
        yield '第三分隔線'
    }
    let iterator = person()
    // console.log(iterator); 列印的就是一個迭代器物件,裡面有一個 next() 方法,我們藉助next方法讓它執行
    iterator.next()
    iterator.next()
    iterator.next()
    // async函數
    const person1 = async function (){
        console.log('hello world');
        await '第一分隔線' 
        console.log('hello world 1');
        await '第二分隔線'
        console.log('hello world 2');
        await '第三分隔線'
    }
    person1()
</script>

async函數的實現原理就是將 Generator 函數和自動執行器包裝在一個函數裡。

<script>
    async function fn(args) {}
    // 等同於
    function fn(args) {
        // spawn函數就是自動執行器
        return spawn(function* () {});
    }
</script>

我們可以分析一下 Generator 和 async 程式碼的書寫特點和風格:

<script>
    // Generator 函數
    function Generator(a, b) {
        return spawn(function*() {
            let r = null;
            try {
                for(let k of b) {
                r = yield k(a);
                }
            } catch(e) {
                /* 忽略錯誤,繼續執行 */
            }
            return r;
        });
    }
    // async 函數
    async function async(a, b) {
        let r = null;
        try {
            for(let k of b) {
            r = await k(a);
            }
        } catch(e) {
         /* 忽略錯誤,繼續執行 */
        }
        return r;
    }
</script>

所以 async 函數的實現符合語意也很簡潔,不用寫Generator的自動執行器,改在語言底層提供,因此程式碼量少。

從上文程式碼我們可以總結以下幾點:

(1)Generator函數執行需要藉助執行器,而async函數自帶執行器,即async不需要像生成器一樣需要藉助 next 方法才能執行,而是會自動執行。

(2)相比於生成器函數,我們可以看到 async 函數的語意更加清晰

(3)上面就說了,async函數可以接受Promise或者其他原始型別,而生成器函數yield命令後面只能是Promise物件或者Thunk函數。

(4)async函數返回值只能是Promise物件,而生成器函數返回值是 Iterator 物件

到此這篇關於JavaScript非同步程式設計中async函數詳解的文章就介紹到這了,更多相關JS async內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com