1. 程式人生 > >async和await對promise非同步方案的改進,以及使用注意事項

async和await對promise非同步方案的改進,以及使用注意事項

async、await相比原生promise的有優勢:

1.更加簡潔,await一個promise即可,那麼會自動返回這個promise的resolve值,無需在then函式的回撥中手動取值,徹底解決了回撥

//Promise方式
 function f() {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve('done!'), 1000)
    })
    promise.then((res) => {
        console.log('object :', res);
    })
}
f()
//async、await
async function f() {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve('done!'), 1000)
    })
    let result = await promise // 直到promise返回一個resolve值(*)
    console.log('object :', result);
}
f()

2.避免了then鏈式呼叫的,沒有設定失敗回撥而導致異常被丟擲到then鏈的末端,而導致被忽略,向下面程式碼一樣,如果then沒有設定失敗回撥,那麼預設的失敗回撥會將異常拋給下一個then函式的失敗回撥,如果末端沒有一個catch函式。那麼異常就會丟失,問題是如果catch程式碼中的異常處理程式碼又有異常丟擲呢,那麼這個異常只能在下一個then中捕獲,這是容易被忽略的錯誤

//promise
let p = new Promise((resolve, reject) => {
    reject()
})
p.then().then().catch()

async任意一個await出現了異常,await會自動丟擲reject,並且程式會被停止,異常統一在try-catch塊可以捕獲而不會出現捕獲鏈無限延長的問題

//async、await
async function f() {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => reject('done!'), 1000)
    })
    try {
        let result = await promise // 直到promise返回一個resolve值(*)
    } catch (error) {
        console.log('object :', error);
    }
}
f()

3.then鏈式流中,資料訪問不能很自然的跨層訪問

MongoClient.connect(url + db_name).then(db=> {
    return db.collection('blogs');
}).then(coll=> {
    return coll.find().toArray();
}).then(blogs=> {
    console.log(blogs.length);
}).catch(err=> {
    console.log(err);

先連線資料庫MongoClient.connect()返回一個Promise,然後在then()方法裡獲得資料庫物件db,然後再獲取到coll物件再返回。在下一個then()方法獲得coll物件,然後進行查詢,查詢結果返回,逐層呼叫then()方法,形成一個Promise鏈。

這時候我們有一個需求,第三個then(blogs => {})中我們只能獲取到查詢的結果blogs,而不能使用上面的db物件和coll物件。這個時候,如果要打印出blogs列表後,要關閉資料庫db.close()怎麼辦?

兩種處理方式:

1.使用then巢狀

MongoClient.connect(url + db_name).then(db=> {
    let coll = db.collection('blogs');
    coll.find().toArray().then(blogs=> {
        console.log(blogs.length);
        db.close();
    }).catch(err=> {
        console.log(err);
    });
}).catch(err=> {
    console.log(err);

問題:
這會打斷Promise鏈,導致then的回撥地獄,而且導致在每一個then中都需要手動捕獲異常,因為then沒成鏈,不能自然傳遞異常

2.每個then()方法裡都將db傳過來

MongoClient.connect(url + db_name).then(db=> {
    return {db:db,coll:db.collection('blogs')};
}).then(result=> {
    return {db:result.db,blogs:result.coll.find().toArray()};
}).then(result=> {
    return result.blogs.then(blogs=> {   //注意這裡,result.coll.find().toArray()返回的是一個Promise,因此這裡需要再解析一層
        return {db:result.db,blogs:blogs}
    })
}).then(result=> {
    console.log(result.blogs.length);
    result.db.close();
}).catch(err=> {
    console.log(err);

問題:
我們在then方法中,都將db和其他結果合併成一個物件,特別需要注意的是,如果傳遞的值含有promise,那麼還需要多做一層解析,也就是需要單獨給予一個then函式進行處理,況且每次都要傳遞一個多餘的物件(對於到達實際使用地方這段路徑,這個物件是不需要使用的)

async、await方案:

let getBlogs = async function(){
    let db = await MongoClient.connect(url + db_name);
    let coll = db.collection('blogs');
    let blogs = await coll.find().toArray();
    db.close();
    return blogs;
};
 
getBlogs().then(result=> {
    console.log(result.length);
}).catch(err=> {
    console.log(err);

這裡await解決了then鏈的問題,使得then跨層訪問的問題從根本上被解決了,因為await的promise的resolve值被置於同一個作用域,可以隨意訪問

4.使得原本非同步非阻塞的表達方式,變成了更加同步阻塞的程式碼,這得益於ES6中生成器和迭代器,賦予js函式的魔力,本質上,async、await是生成器和迭代器以及Promise結合的語法糖,它使得promise之前設計缺陷被更好地修正,目前看來,async、await,是非同步的終極解決方案之一

async function basicDemo() {
    let result = await Math.random();
    console.log(result);
}

basicDemo();

await由於自動返回了resolve的值,無需then,我們甚至沒有感知到非同步的存在,他將非同步從語法層面上進行了同步化

async、await使用注意事項:

1.await雖然可以像Promise.resolve作用域很多型別的資料,但它的主要意圖是用來等待Promise物件被resolved,如果await是非promise值,那麼會被立即執行

2.async函式將返回值自動包裝成一個promise,就像Promise.resolve一致的行為

3.await必須在async函式上下文,也就是如下程式碼的區別

// 正常 for 迴圈
async function forDemo() {
    let arr = [1, 2, 3, 4, 5];
    for (let i = 0; i < arr.length; i ++) {
        await arr[i];
    }
}
forDemo();//正常輸出
// 因為想要炫技把 for迴圈寫成下面這樣
async function forBugDemo() {
    let arr = [1, 2, 3, 4, 5];
    arr.forEach(item => {
        await item;
    });
}
forBugDemo();// Uncaught SyntaxError: Unexpected identifier

4.小心自己的並行處理,也許不小心就將ajax的併發請求發成了阻塞式同步的操作,理解這句話的核心是: await若等待的是promise,那麼程式就會在此處等到promise的resolved,然後繼續往下,看下面例子,這裡第一個sleep會等待自身resolved完成才會往下,如果我們可以讓這些函式並行,同時保持await的特性,那麼效率會大大提高

function sleep(second) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('request done! ' + Math.random());
        }, second);
    })
}

async function bugDemo() {
    await sleep(1000);
    await sleep(1000);
    await sleep(1000);
    console.log('clear the loading~');
}

bugDemo();

正確的姿勢是:

async function correctDemo() {
    let p1 = sleep(1000);
    let p2 = sleep(1000);
    let p3 = sleep(1000);
    await Promise.all([p1, p2, p3]);//這裡單獨await每一個promise也是一樣的效果
    console.log('clear the loading~');
}
correctDemo();// clear the loading~