javascript異步流程處理幾種方式

前言
JavaScript 提倡書寫異步方法, 這樣可以更好地利用事件隊列機制, 來高效的無阻塞的運行應用。但這容易帶來了大量的異步回調嵌套,常常就會出現俗稱“回調地獄(callback hell)”的現象。

特別的當多個異步要求按照順序執行,其結果多級依賴時,多級的回調嵌套不僅使得代碼變難看難懂,更使得調試、重構的過程異常復雜。所以就有了異步流程控制,可以更方便的用看似同步的方法來寫異步的東西,讓程序邏輯清晰易懂。下面就來談談異步流程處理的幾種方式。

一、回調函數的方式

例如1,你會遇到這樣的一種需求,獲取一組地址信息,但是異步返回的是對應地址的code,拿到該數據后,需要對每一項轉換成中文地名,全部轉換完成后返回頁面。

// 獲取一組地址信息
$.get('/api/getAddressData',{},function(data){
    let list = data.addressList;
    // 在回調函數中,對轉換好的數據進行操作
    let transfer = after(list.length,function(data){
        console.log('轉換好了',data)
    })
    for(let i =0; i< list.length; i++){
        let item = list.[i]
        transfer(item);
    }
});

function after(times,callback){
    let list = [];
    return function(data){
        // 將數據進行轉換
        $.get('/api/getTransferAddress',{code:item.code},function(data){
            data['addressName'] = data.addressName;
            list.push(data);
            // 所有數據轉換完成后,調用回調函數,把轉換好的數據返回給回調中使用
            if(--times === 0){
                callback(list);
            }
        })
    }
}

例如2,文件讀取 異步i/o。1.txt,2.txt,3.txt的結果多級依賴。這種方式的做法就是在回調函數中,執行下一次的異步事務,依次嵌套執行。

// 讀取 異步i/o
let fs = require('fs');
fs.readFile('./1.txt','utf8',function(err,data){  // 先讀取1.txt
    if(err) return console.log(err);
    fs.readFile(data,'utf8',function(err,data){ //從1.txt得到的下一個要讀取的文件的路徑==> ./2.txt
        if(err) return console.log(err);
        console.log(data);
        fs.readFile(data,'utf8',function(err,data){ //從2.txt得到的下一個要讀取的文件的路徑==> ./3.txt
           if(err) return console.log(err);
           console.log(data);
        });
    });
});

從以上代碼可以看出,采用回調函數進行異步處理的方式,代碼邏輯比較復雜,不易于閱讀,而且常常容易出現“回調地獄”的寫法。

二、promise 方式

1、什么是promise?

Promise是異步編程的一種解決方案,它有三種狀態,分別是pending-進行中、resolved-已完成、rejected-已失敗。

在2015年6月, ES2015(即 ECMAScript 6、ES6) 正式發布。其中 Promise 被列為正式規范,成為 ES6 中最重要的特性之一。在此之前使用promise一般都是借用第三方庫,例如常用的q庫、co庫、bluebird等。

優點:可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操作更加容易。
缺點:
1)無法取消 Promise,一旦新建它就會立即執行,無法中途取消
2)無如果不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部
3)當處于 Pending 狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)

2、基本API
  • Promise#then
// 基本用法
promise.then(onFulfilled, onRejected);
let promise = new Promise(function(resolve, reject) {
    setTimeout(function(){
        resolve("成功的數據");
    },10)
});
promise.then(function(value) {
    console.log(value); // ==> 成功的數據
}).catch(function(error) {
    console.log(error);
});
  • Promise#catch
// 捕獲錯誤的方法,then沒有傳遞錯誤函數,過程中拋出的錯誤,都能捕獲到;
// 等價于 promise.then(undefined, onRejected) 的語法糖
promise.then(function(value) {
    console.log(value); 
}).then(function(){
    console.log(value); 
}).catch(function(error) {
    console.log(error);
});

  • Promise#resolve
// Promise的成功靜態方法
Promise.resolve([1,2,3]).then(function(data){
    console.log(data);
});
  • Promise#reject
// Promise的失敗靜態方法
Promise.reject(new Error("error"));
  • Promise#all
    生成并返回一個新的promise對象。
    Promise.all接收的參數是一個數組,參數里面放置的是promise對象,最后返回的數據也是按照參數順序,返回一個數組,對應的值就是對應的promise對象成功返回的值
    ** 注意:all的方法是所有promise都成功了,才算成功,否則就算失敗
let p1 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據1");
    },10)
})
let p2 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據2");
    },10)
})
let p3 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據2");
    },10)
})

Promise.all([p1,p2,p3]).then(function(data){
  console.log(data)  // ==>["異步數據1", "異步數據2", "異步數據2"]
}, function(err){
  console.log(err)
})
  • Promise#race
    生成并返回一個新的promise對象。
    參數 promise 數組中的任何一個promise對象如果變為resolve或者reject的話, 該函數就會返回,并使用這個promise對象的值進行resolve或者reject
    ** 注意:只要有一個promise成功了 就算成功。如果第一個失敗了就失敗了
let p1 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據1");
    },10)
})
let p2 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據2");
    },10)
})
let p3 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據2");
    },10)
})

Promise.race([p1,p2,p3]).then(function(data){
  console.log(data) // ==>"異步數據1"  取決于promise的執行時間,返回第一個成功的promise的結果
}, function(err){
  console.log(err)
})

三、generator方式

generator(生成器)是ES6標準引入的新的數據類型。一個generator看上去像一個函數,但可以返回多次。原理是將一個函數劃分成若干個小函數,每次調用時移動指針,內部是一個條件判斷,執行對應的邏輯

  • genrator 函數要用* 來比標識,yield(暫停 產出)
  • 它會將函數分割成部分,調用一次next就會繼續向下執行
  • 返回結果是一個迭代器 迭代器有一個next方法
  • yield后面跟著的是value的值,異步一般跟著的是promise對象
  • yield等號前面的是我們當前調用next傳進來的值
  • 第一次next傳值是無效的

異步 generator主要和promise搭配使用,co庫可以自動的將generator進行迭代

// 使用generator處理異步流程,讓p1和p2兩個異步事務順序執行
// 首先安裝co庫 npm install co

let co = require('co');
let p1 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據1");
    },10)
})
let p2 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據2");
    },10)
})

function *getData(){
    let list = []
    let a = yield p1;
    list.push(a)
    let b = yield p2;
    list.push(b)
    return list
}
// co庫自動進行迭代
co(getData()).then(function (data) {
    console.log(data) // ==>["異步數據1", "異步數據2"]
})

四、async / await 方式

在最新的ES7(ES2017)中提出的前端異步特性:async、await。
async用來修飾函數,聲明一個函數是異步的。而await是用于等待異步完成。并且await只能在async函數中使用。

實際上async和await(語法糖) 相當于 co + generator,但是少了co的靈活, co不僅可以包裝Promise還可以包裝數組, 還可以包裝thunk, 而await自動包裝后的東西只能是原始值, 或者 Promise

通常async、await都是和Promise一起使用的。
因為async返回的都是一個Promise對象同時async適用于任何類型的函數上。這樣await得到的就是一個Promise對象(如果不是Promise對象的話那async返回的是什么 就是什么);

await得到Promise對象之后就等待Promise接下來的resolve或者reject。
那么async / await 解決的問題有哪些呢?
1.回調地獄
2.并發執行異步,在同一時刻同步返回結果 Promise.all
3.解決了返回值的問題
4.可以實現代碼的try/catch;


let p1 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據1");
    },10)
})
let p2 = new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve("異步數據2");
    },10)
})

async function getData(){
    let list = [];
    try{
        let content1 = await p1;
        list.push(content1);  // content1就是p1這個promise對象resovle返回的數據
        let content2 = await p2;
        list.push(content2); // content2就是p2這個promise對象resovle返回的數據
        return list
    }catch(e){ // 如果出錯會catch
        console.log('err',e)
    }
}

// async函數返回的是promise,
getData().then(function(data){
    console.log(data); // ==>["異步數據1", "異步數據2"]
},function(err){
    console.log(err)
})
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容

  • 異步編程對JavaScript語言太重要。Javascript語言的執行環境是“單線程”的,如果沒有異步編程,根本...
    呼呼哥閱讀 7,323評論 5 22
  • 歡迎閱讀專門探索 JavaScript 及其構建組件的系列文章的第四章。 在識別和描述核心元素的過程中,我們還分享...
    OSC開源社區閱讀 1,159評論 1 10
  • 你不知道JS:異步 第二章:回調(Callbacks) 在第一章中,我們探究了JS中異步編程方面的一些術語和概念。...
    purple_force閱讀 1,179評論 0 2
  • 懂,卻不投其所好逢迎;會,不屑巧言令色違心。 此種性格的人說是率真也好抑或是個傻子也罷,若非是深交摯友,往往是難討...
    淺醉輕抒閱讀 320評論 0 1
  • 當大衛還像小貓一樣時,我惆悵著愛著它,甚至期盼它永遠小小的,這樣就能滿足一顆怕狗又想愛狗的心。然后,朋友...
    yvetteye1閱讀 237評論 0 0