撩課-Web架構師養成系列(第二篇)-async

前言

Web架構師養成系列共15篇,每週更新一篇,主要分享、探討目前大前端領域(前端、後端、移動端)企業中正在用的各類成熟的、新的技術。部分文章也會分析一些框架的底層實現,讓咱們作到知其然知其因此然。前端

本篇爲第二篇,上一篇:撩課-Web架構師養成系列第一篇ajax

本篇文章閱讀須要時長:約15分鐘編程

1、先了解異步?

關於"異步",咱們能夠這麼理解: 一個任務拆分紅兩段,先執行第一段,而後轉而執行其餘任務,等到某個時間點,再回過頭執行第二段。後端

好比,你要作土豆燉牛肉,當開始煮牛肉的時候發現土豆沒了,能夠先讓牛肉煮着,而後去買土豆、洗好、切好,再把土豆放到鍋裏一塊兒煮。api

土豆燉牛肉
土豆燉牛肉

這種不連續的執行,就叫作異步。相應地,若是是連續的執行,那麼就叫作同步。數組

1.1 JS中常見的異步編程方式?

異步編程的目標就是讓代碼的執行更加相似於同步編程,開發中比較經常使用的方式主要包括:

1) 回調函數實現
2) 發佈訂閱、通知
3) 事件監聽
4)Promise/A+ 和 生成器函數
5async/await
在ES6以前,咱們更多地是使用回調函數來實現異步編程。

 

1.2 認識回調函數

回調函數就是把任務拆解成兩部分,把任務的第二部份單獨寫在一個函數裏面,等到執行完其它任務從新執行這個任務的時候,就直接調用這個函數,從而達到異步效果。

案例以下:

/**
 * 土豆燉牛肉
 * @param step1 牛肉
 * @param callback 回調
 */
let cook = (step1, callback) => {
    // 1. 煮牛肉
    console.log(`燒水煮${step1}`);
    //  2. 放入土豆(5秒後執行)
    setTimeout(() => {
        let step2 = '放入土豆';
        callback(step2);
    }, 5000)
};

// 1. 先煮牛肉  
cook('牛肉', (data) => {
    console.log(data);
});

// 2. 作其它事, 5s後放入土豆 
console.log('買土豆');
console.log('洗土豆');
console.log('切土豆');

 

運行結果
運行結果
雖然回調函數可以實現異步,可是回調函數存在如下問題:

1) 回調地獄問題
異步多級依賴的狀況下會層層嵌套,代碼難以閱讀的維護;

2)可能會形成多個異步在某一時刻獲取全部異步的結果;

3) 異步不支持try/catch
回調函數是在下一事件環中取出,
因此通常在回調函數的第一個參數都是用來預置錯誤對象

4)不能經過return返回結果

 

回調地獄(圖片來源於網絡)
回調地獄(圖片來源於網絡)

2、異步改進方案-Promise

2.1 什麼是Promise?

promise,承諾。在代碼中咱們能夠這麼理解:此處我先許下個承諾,過了必定時間後我帶給你一個結果。promise

那麼,在這一段時間中作什麼?
咱們能夠進行異步操做,好比請求網絡數據、耗時運算、讀寫本地文件等

 

 

2.2 Promise的三種狀態?

1) Pending 
   Promise對象實例建立時候的初始狀態
2) Fulfilled 
   成功的狀態
3) Rejected 
   失敗的狀態 
好比:你發郵件給老闆說要加工資,這時候你就要"等待"他的郵件回覆,他能夠立馬給你回覆,若是贊成了,表示"成功";若是不一樣意,表示"失敗",固然他也能夠一直不一樣意;可是這期間不影響你作其它事情。

在實際開發中,咱們能夠經過then 方法,來指定Promise 對象的狀態改變時肯定執行的操做,resolve 時執行第一個函數(onFulfilled),reject 時執行第二個函數(onRejected)。

來,一塊兒認識下promise的幾種操做方式和經常使用方法:

構建Promise
//  promise的方法會馬上執行;
//  兩個輸出都會打印
let promise = new Promise(() => {
     console.log('喜歡IT');
});
console.log('就上撩課');
promise也能夠表明將來的一個值
一個promise實例能夠屢次調用then,當成功後會將結果依次執行。

let  promise = new Promise((resolve, reject) => {
    ajax.get(BASEURL + 'api/goods/', (err, data)=>{
        if (err) return reject(err);
        resolve(data);
    })
});
promise.then(data => {
    console.log(data);
});
promise.then(data => {
    console.log(data);
});
promise也能夠表明一個不用返回的值
 // 表明一個用於不會返回的值
let  promise = new Promise((resolve, reject) => { });
promise.then(data => {
    console.log(data);
});
Promise.resolve
返回一個Promise實例,這個實例處於resolve狀態。

Promise.resolve('成功獲取結果').then(data=>{ 
    console.log(data);
});
Promise.reject
返回一個Promise實例,這個實例處於reject狀態。

Promise.reject('獲取結果失敗').then(data=>{ 
   console.log(data); 
},err=>{ 
console.log(err); 
})
Promise.race
該方法用於接收一個數組,數組內都是Promise實例,返回一個Promise實例,這個Promise實例的狀態轉移取決於參數的Promise實例的狀態變化。

當參數中任何一個實例處於resolve狀態時,返回的Promise實例會變爲resolve狀態。若是參數中任意一個實例處於reject狀態,返回的Promise實例變爲reject狀態。

Promise.race(
    [readFiles('./a.txt'),
    readFiles('./b.txt')]).then(data=>{
    console.log({data})
},(err)=>{
    console.log(err)
});

 

2.3 案例實操

咱們再用promise實現上面發郵件加工資的案例:微信

狀況一 :
在必定時間後(假設5s後),老闆回覆了郵件,能夠是如下兩種狀況:

let addWages = ()=>{
    return new Promise((resolve, reject) => {
        setTimeout(function  ()  {
            // 公司帳戶餘額
            let  currentMoney = 9999999999;
            // 公司帳戶餘額 > 100w
            if (currentMoney > 1000000) {
                resolve('贊成加薪');
            } else {
                resolve('不一樣意加薪');
            }
        }, 5000)
    })
};
addWages().then(data => {
    console.log(data);
}, data => {
    console.log(data);
});

//  運行結果:贊成加薪
狀況二 :
公司帳戶已經沒錢,無法加工資了,表現形式以下:

let addWages = ()=>{
    return new Promise((resolve, reject) => {
        throw new Error('你表現不夠優秀!');
    })
};
addWages().then(data => {
    console.log(data);
}, data => {
    console.log('這裏輸出:' + data);
});
咱們能夠採用then的第二個參數捕獲reject返回結果或者捕獲失敗,固然也能夠經過.catch函數進行捕獲。

 

3、promise能夠解決回調函數帶來的問題

前面的案例描述已經驗證了promise支持catch,此外,經過promise也可以返回結果給外部。咱們再一塊兒看看promise如何解決回調地獄和同步異步結果。網絡

3.1 解決回調地獄

案例場景:在文檔a.txt中存放正文檔b.txt的路徑,在文檔b.txt中存放正文檔c.txt的路徑, 咱們要取出文檔c.txt裏面的內容。

構造函數實現:

/* a.txt -> b.txt -> c.txt -> 輸出內容*/
let fs = require('fs');
let readFiles = ()=>{
    // 回調1
    fs.readFile('./a.txt','utf8', (err,data)=>{
        if(err) return console.log(err);
        // 回調2
        fs.readFile(data,'utf8',function(err,data){
            if(err) return console.log(err);
            // 回調3
            fs.readFile(data,'utf8',function(err,data){
                if(err) return console.log(err);
                console.log(data); 
            })
        })
    })
};

/*
調用輸出結果:
喜歡IT, 就上撩課(itlike.com) 
*/
readFiles();
經過promise解決回調地獄:

let fs = require('fs');

// 1. 初始化promise
let readFiles =(filePath)=>{
    return new Promise((resolve,reject)=>{
        fs.readFile(filePath,'utf8',(err,data)=>{
            if(err) return reject(err);
            resolve(data);
        })
    })
};

// 2. 相似於鏈式的調用方式
readFiles('./a.txt').then((data)=>{
    return readFile(data);
}).then((data)=>{
    // 獲取b.txt中內容
    return readFile(data);
}).then((data)=>{
    // 輸出c.txt中內容
    console.log(data)  
}).catch((err)=>{
    console.log(err)
});
3.2 在同一時刻同步全部異步產生的結果
該場景在實際開發中有不少應用場景,好比:咱們要提交一個操做時,須要結合以前的兩個異步請求的結果才能進行。

再好比:你要進行下一個運算時,須要前面兩個異步運算的結果才能進行。咱們仍是經過讀取文件的案例來進行舉例。

常規方式實現:

let fs = require('fs');
// 1. 統一輸出全部異步產生的結果
let allContent = {};
let logAllContent = (key,data)=>{
    allContent[key] = data;
    if(Object.keys(allContent).length === 2){
        console.log(allContent)
    }
};

// 2. 分別異步讀取文件中的內容
fs.readFile('./a.txt', 'utf8', function (err, data) {
    if (err) return console.log(err);
    logAllContent(data);
});
fs.readFile('./b.txt', 'utf8', function (err, data) {
    if (err) return console.log(err);
    logAllContent(data);
});
這樣的方式雖然解決了問題,可是你不知道最終結果是在哪一個異步函數中輸出,並且你須要在全部的異步函數中都去調用打印方法。

promise方式大大簡化:

藉助promise.all()方法,無論哪一個promise誰先完成,該方法會按照數組裏面的順序將結果返回。

let fs = require('fs');
let readFiles = (filePath)=>{
    return new Promise(function(resolve,reject){
        fs.readFile(filePath,'utf8', (err,data)=>{
            if(err) return reject(err);
            resolve(data);
        })
    })
};

Promise.all(
    [readFiles('./a.txt'), 
     readFiles('./b.txt')]
).then(([data])=>{
    console.log({data})
});

 

後續

藉助Promise已經能夠幫助咱們很好解決異步編程的問題,但還不是那麼的行雲流水、一鼓作氣,咱們更但願編寫異步代碼可以像寫同步代碼同樣直觀、簡單。架構

在下一篇咱們會講些更好、更靈活的異步編程方案,敬請期待。獲取資料、交流可加我微信:yejh9522 一塊兒探討學習。

相關文章
相關標籤/搜索