成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript 異步進(jìn)化史

luzhuqun / 2957人閱讀

摘要:簽訂協(xié)議的兩方分別是異步接口和。在異步函數(shù)中,使用異常捕獲的方案,代替了的異常捕獲的方案。需要注意的是,在異步函數(shù)中使異步函數(shù)用時(shí)要使用,不然異步函會被同步執(zhí)行。

同步與異步

通常,代碼是由上往下依次執(zhí)行的。如果有多個(gè)任務(wù),就必需排隊(duì),前一個(gè)任務(wù)完成,后一個(gè)任務(wù)才會執(zhí)行。這種執(zhí)行模式稱之為: 同步(synchronous) 。新手容易把計(jì)算機(jī)用語中的同步,和日常用語中的同步弄混淆。如,“把文件同步到云端”中的同步,指的是“使...保持一致”。而在計(jì)算機(jī)中,同步指的是任務(wù)從上往下依次執(zhí)行的模式。比如:

例 1

A();
B();
C();

在上述代碼中,A、B、C 是三個(gè)不同的函數(shù),每個(gè)函數(shù)都是一個(gè)不相關(guān)的任務(wù)。在同步模式下,計(jì)算機(jī)會先執(zhí)行 A 任務(wù),再執(zhí)行 B 任務(wù),最后執(zhí)行 C 任務(wù)。在大部分情況,同步模式都沒問題。但是如果 B 任務(wù)是一個(gè)耗時(shí)很長網(wǎng)絡(luò)的請求,而 C 任務(wù)恰好是展現(xiàn)新頁面,B 與 C 沒有依賴關(guān)系。這就會導(dǎo)致網(wǎng)頁卡頓的現(xiàn)象。有一種解決方案,將 B 放在 C 后面去執(zhí)行,但唯一有些不足的是,B 的網(wǎng)絡(luò)請求會遲一些再發(fā)送。

還有另一種更完美解決方案,將 B 任務(wù)分成的兩個(gè)部分。一部分是,立即執(zhí)行網(wǎng)絡(luò)請求的任務(wù);另一部分是,在請求數(shù)據(jù)回來后執(zhí)行的任務(wù)。這種一部分在立即執(zhí)行,另一部分在未來執(zhí)行的模式稱為 異步(asynchronous) 。偽代碼如下:

例 2

A();
// 在現(xiàn)在發(fā)送請求
ajax("url1",function B() {
  // 在未來某個(gè)時(shí)刻執(zhí)行
})
C();
// 執(zhí)行順序 A => C => B

實(shí)際上,JavaScript 引擎先執(zhí)行了調(diào)用了瀏覽器的網(wǎng)絡(luò)請求接口的任務(wù)(一部分任務(wù)),再由瀏覽器發(fā)送網(wǎng)絡(luò)請求并監(jiān)聽請求返回(這個(gè)任務(wù)不由 JavaScript 引擎執(zhí)行,而是瀏覽器);等請求放回后,瀏覽器再通知 JavaScript 引擎,開始執(zhí)行回調(diào)函數(shù)中的任務(wù)(另一部分)。JavaScript 異步能力的本質(zhì)是瀏覽器或 Node 的多線程能力。

callback

未來執(zhí)行的函數(shù)通常也叫 callback。使用 callback 的異步模式,解決了阻塞的問題,但是也帶了一些其他問題。在最開始,我們的函數(shù)是從上往下書寫的,也是從上往下執(zhí)行的,這非常符合我們的思維習(xí)慣,但是現(xiàn)在卻被 callback 打斷了!在上面一段代碼中,它跳過 B 任務(wù),先執(zhí)行了 C任務(wù)!這種異步“非線性”的代碼會比同步“線性”的代碼,更難閱讀,因此也更容易滋生 BUG。

試著判斷下面這段代碼的執(zhí)行順序,你會對“非線性”代碼比“線性”代碼更難以閱讀,體會更深。

例 3

A();
ajax("url1", function(){
    B();
    ajax("url2", function(){
        C();
    }
    D();
});
E();

// 下面是答案,你猜對了嗎?
// A => E => B => D => C

在例 3 中,我們的閱讀代碼視線是 A => B => C => D => E ,但是執(zhí)行順序卻是 A => E => B => D => C 。從上往下執(zhí)行的順序被 Callback 打亂了,這就是非線性代碼帶來的糟糕之處。

上面的例子中,我們可以通過將 ajax 后面執(zhí)行的任務(wù) E 和 任務(wù) D 提前,來進(jìn)行代碼優(yōu)化。這種技巧在寫多重嵌套的代碼時(shí),是非常有用的。改進(jìn)后,如下。

例 4

A();
E();
ajax("url1", function(){
    B();
    D();
    ajax("url2", function(){
        C();
    }
});
// 稍作優(yōu)化,代碼更容易看懂
// A => E => B => D => C

在例 4 中,只有處理了成功回調(diào),并沒處理異?;卣{(diào)。接下來,把異常處理回調(diào)加上,再來討論代碼“線性”執(zhí)行的問題。

例 5

A();

ajax("url1", function(){
    B();

    ajax("url2", function(){
        C();
    },function(){
        D();
    });

},function(){
    E();

});

例 5 中,加上異常處理回調(diào)后,url1 的成功回調(diào)函數(shù) B 和異?;卣{(diào)函數(shù) E,被分開了。這種“非線性”的情況又出現(xiàn)了。

在 node 中,為了解決的異常處理“非線性”的問題,制定了錯(cuò)誤優(yōu)先的策略。node 中 callback 的第一個(gè)參數(shù),專門用于判斷是否發(fā)生異常。

例 6

A();

get("url1", function(error){
    if(error){
        E();
    }else {
        B();

        get("url2", function(error){
            if(error){
                D();
            }else{
                C();
            }
        });
    }
});

到此,callback 引起的“非線性”問題基本得到解決。遺憾的是,一旦嵌套層數(shù)多起來,閱讀起來還不是很方便。此外,callback 一旦出現(xiàn)異常,只能在當(dāng)前回調(diào)內(nèi)部處理異常,并沒有一個(gè)整體的異常觸底方案。

promise

在 JavaScript 的異步進(jìn)化史中,涌現(xiàn)出一系列解決 callback 弊端的庫,而 Promise 成為了最終的勝者,并成功地被引入了 ES6 中。它將提供了一個(gè)更好的“線性”書寫方式,并解決了異步異常只能在當(dāng)前回調(diào)中捕獲的問題。

Promise 就像一個(gè)中介,它承諾會將一個(gè)可信任的異步結(jié)果返回。簽訂協(xié)議的兩方分別是異步接口和 callback。首先 Promise 和異步接口簽訂一個(gè)協(xié)議,成功時(shí),調(diào)用 resolve 函數(shù)通知 Promise,異常時(shí),調(diào)用 reject 通知 Promise。另一方面 Promise 和 callback 也簽訂一個(gè)協(xié)議,當(dāng)異步接口的 resolvereject 被調(diào)用時(shí),由 Promise 返回可信任的值給 thencatch 中注冊的 callback。

一個(gè)最簡單的 promise 示例如下:

例 7

// 創(chuàng)建一個(gè) Promise 實(shí)例(異步接口和 Promise 簽訂協(xié)議)
var promise = new Promise(function (resolve,reject) {
  ajax("url",resolve,reject);
});

// 調(diào)用實(shí)例的 then catch 方法 (成功回調(diào)、異常回調(diào)與 Promise 簽訂協(xié)議)
promise.then(function(value) {
  // success
}).catch(function (error) {
  // error
})

Promise 是個(gè)非常不錯(cuò)的中介,它只返回可信的信息給 callback。怎么理解可信的概念呢?準(zhǔn)確的講,就是 callback 一定會被異步調(diào)用,且只會調(diào)用一次。比如在使用第三方庫的時(shí)候,由于某些原因,(假的)“異步”接口不可靠,它執(zhí)行了同步代碼,而沒有進(jìn)入異步邏輯,如例 8。

例 8

var promise1 = new Promise(function (resolve) {
  // 由于某些原因?qū)е隆爱惒健苯涌?,被同步?zhí)行了
  if (true ){
    // 同步代碼
    resolve("B");
  } else {
    // 異步代碼
    setTimeout(function(){
      resolve("B");
    },0)
  }

});

// promise依舊會異步執(zhí)行
promise1.then(function(value){
    console.log(value)
});

console.log("A");
// A => B (先 A 后 B)

再比如,由于某些原因,異步接口不可靠,resolvereject 被執(zhí)行了兩次。但 Promise 只會通知 callback ,第一次異步接口返回的結(jié)果。如例 9:

例 9

var promise2 = new Promise(function (resolve) {
  // resolve 被執(zhí)行了 2 次
  setTimeout(function(){
    resolve("第一次");
  },0)
  setTimeout(function(){
    resolve("第二次");
  },0)
});

// 但 callback 只會被調(diào)用一次,
promise2.then(function(msg){
    console.log(msg) // "第一次"
    console.log("A")
});
// A (只有一個(gè))

介紹完 Promise 的特性后,來看看它如何利用鏈?zhǔn)秸{(diào)用,解決 callback 模式下,異步代碼可讀性的問題。鏈?zhǔn)秸{(diào)用指的是:函數(shù) return 一個(gè)可以繼續(xù)執(zhí)行的對象,該對象可以繼續(xù)調(diào)用,并且 return 另一個(gè)可以繼續(xù)執(zhí)行的對象,如此反復(fù)達(dá)到不斷調(diào)用的結(jié)果。如例 10:

例 10

// return 一個(gè)可以繼續(xù)執(zhí)行的 Promise 對象
var fetch = function(url){
    return new Promise(function (resolve,reject) {
        ajax(url,resolve,reject);
    });
}

A();
fetch("url1").then(function(){
    B();
    // 返回一個(gè)新的 Promise 實(shí)例
    return fetch("url2");
}).catch(function(){
    C();
    // 異常的時(shí)候也可以返回一個(gè)新的 Promise 實(shí)例
    return fetch("url2");
    // 使用鏈?zhǔn)綄懛ㄕ{(diào)用這個(gè)新的 Promise 實(shí)例的 then 方法
}).then(function() {
    // 可以繼續(xù) return,也可以不繼續(xù) return,結(jié)束鏈?zhǔn)秸{(diào)用
    D();
})
// A B C D (順序執(zhí)行)

如此反復(fù),不斷返回一個(gè) Promise 對象,使 Promise 擺脫了 callback 層層嵌套的問題和異步代碼“非線性”執(zhí)行的問題。

另外,Promise 還解決了一個(gè)難點(diǎn),callback 只能捕獲當(dāng)前錯(cuò)誤異常。Promise 和 callback 不同,每個(gè) callback 只能知道自己的報(bào)錯(cuò)情況,但 Promise 代理著所有的 callback,所有 callback 的報(bào)錯(cuò),都可以由 Promise 統(tǒng)一處理。所以,可以通過在最后設(shè)置一個(gè) catch 來捕獲之前未捕獲異常。

Promise 解決 callback 的異步調(diào)用問題,但 Promise 并沒有擺脫 callback,它只是將 callback 放到一個(gè)可以信任的中間機(jī)構(gòu),這個(gè)中間機(jī)構(gòu)去鏈接 callback 和異步接口。此外,鏈?zhǔn)秸{(diào)用的寫法并不是非常優(yōu)雅。接下來介紹的異步(async)函數(shù)方案,會給出一個(gè)更好的解決方案。

異步(async)函數(shù)

異步(async)函數(shù)是 ES7 的一個(gè)新的特性,它結(jié)合了 Promise,讓我們擺脫 callback 的束縛,直接用“同步”方式,寫異步函數(shù)。注意,這里的同步指的是寫法同步,但實(shí)際依舊是異步執(zhí)行的。

聲明異步函數(shù),只需在普通函數(shù)前添加一個(gè)關(guān)鍵字 async 即可,如:

async function main(){}

在異步函數(shù)中,可以使用 await 關(guān)鍵字,表示等待后面表達(dá)式的執(zhí)行結(jié)果,再往下繼續(xù)執(zhí)行。表達(dá)式一般都是 Promise 實(shí)例。如,例 11:

例 11

var  timer = function (delay) {
  return new Promise(function create(resolve,reject) {
    if(typeof delay !== "number"){
      reject(new Error("type error"));
    }
    setTimeout(resolve,delay,"done");
  });
}

async function main{
    var value = await timer(100);
    // 不會立刻執(zhí)行,等待 100ms 后才開始執(zhí)行
    console.log(value);  // done
}

main();

異步函數(shù)和普通函數(shù)的調(diào)用方式一樣,最先執(zhí)行 main() 函數(shù)。之后,會立即執(zhí)行 timer(100) 函數(shù)。等到( await )后面的 promise 函數(shù)( timer(100) )返回結(jié)果后,程序才會執(zhí)行下一行代碼。

異步函數(shù)和普通函數(shù)寫法基本類似,除了前面提到的聲明方式類似和調(diào)用方式一樣之外,它也可以使用 try...catch 來捕捉異常,也可以傳入?yún)?shù)。但在異步函數(shù)中使用 return 是沒有作用的,這和普通的 callback 函數(shù) return 沒有作用是一樣原因。callback 或者異步函數(shù)是多帶帶放在 JavaScript 棧(stack)中執(zhí)行的,這時(shí)同步代碼已經(jīng)執(zhí)行完畢。

在異步函數(shù)中,使用 try...catch 異常捕獲的方案,代替了 Promise catch 的異常捕獲的方案。示例如下:

例 12

async function main(delay){
  try{
    // timer 在例 11 中有過聲明
    var value1 = await timer(delay);
    var value2 = await timer("");
    var value3 = await timer(delay);
  }catch(err){
    console.error(err);
      // Error: type error
      //   at create (:5:14)
      //   at timer (:3:10)
      //   at A (:12:10)
  }
}
main(0);

更神奇的是,異步函數(shù)也遵循,“函數(shù)是第一公民”的準(zhǔn)則。也可以當(dāng)作值,傳入普通函數(shù)和異步函數(shù)中執(zhí)行。需要注意的是,在異步函數(shù)中使異步函數(shù)用時(shí)要使用 await,不然異步函會被同步執(zhí)行。例子如下:

例 12

async function doAsync(delay){
    // timer 在例 11 中有過聲明
    var value1 = await timer(delay);
    console.log("A")
}

async function main(main){
  doAsync(0);
  console.log("B")
}

main(main);
// B A

這個(gè)時(shí)候打印出來的值是 B A。說明 doAsync 函數(shù)中的 await timer(delay) 并被同步執(zhí)行了。如果要正確異步地執(zhí)行 doAsync 函數(shù),需要該函數(shù)之前添加 await 關(guān)鍵字,如下:

async function main(delay){
    var value1 = await timer(delay);
    console.log("A")
}

async function doAsync(main){
    await main(0);
    console.log("B")
}

doAsync(main);
// A B

由于異步函數(shù)采用類同步的書寫方法,所以在處理多個(gè)并發(fā)請求,新手可能會像下面一樣書寫:

例 13

var fetch = function (url) {
  return new Promise(function (resolve,reject) {
    ajax(url,resolve,reject);
  });
}

async function main(){
  try{
    var value1 = await fetch("url1");
    var value2 = await fetch("url2");
    conosle.log(value1,value2);
  }catch(err){
    console.error(err)
  }
}

main();

但這樣會導(dǎo)致 url2 的請求必需等到 url1 的請求回來后才會發(fā)送。如果 url1url2 沒有相互的依賴關(guān)系,將這兩個(gè)請求同時(shí)發(fā)送實(shí)現(xiàn)的效果會更好。

Promise.all 的方法,可以很好的處理并發(fā)請求。Promise.all 接受將多個(gè) Promise 實(shí)例為參數(shù),并將這些參數(shù)包裝成一個(gè)新的 Promise 實(shí)例。這樣,Promise.all 中所有的請求會第一時(shí)間發(fā)送出去;在所有的請求成功回來后才會觸發(fā) Promise.allresolve 函數(shù);當(dāng)有一個(gè)請求失敗,則立即調(diào)用 Promise.allreject 函數(shù)。

var fetch = function (url) {
  return new Promise(function (resolve, reject) {
    ajax(url, resolve, reject);
  });
}

async function main(){
  try{
    var arrValue = await Promise.all[fetch("url1"),fetch("url2")];
    conosle.log(arrValue[0], arrValue[1]);
  }catch(err){
    console.error(err)
  }
}

main();

最后對異步函數(shù)的內(nèi)容做個(gè)小結(jié):

聲明: async function main(){}

異步函數(shù)邏輯:可以使用 await

調(diào)用: main()

捕獲異常: try...catch

傳入?yún)?shù): main("第一個(gè)參數(shù)")

return:不生效

異步函數(shù)作為參數(shù)傳入其他函數(shù):可以

處理并發(fā)邏輯:Promise.all

目前使用最新的 Chrome/node 已經(jīng)支持 ES7 異步函數(shù)的寫法了,另外也可以通過 Babel 以將異步函數(shù)轉(zhuǎn)義為 ES5 的語法執(zhí)行。大家可以自己動(dòng)手試試,使用異步函數(shù),用類同步的方式,書寫異步代碼。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90840.html

相關(guān)文章

  • javascript 閉包 ---- js進(jìn)化之路

    摘要:閉包利用的,其實(shí)就是作用域嵌套情況下,內(nèi)部作用域可以訪問外部作用域這一特性。之所以要將閉包和垃圾回收策略聯(lián)系在一起,是因?yàn)檫@涉及到閉包的一些重要特性,如變量暫時(shí)存儲和內(nèi)存泄漏。因?yàn)槟涿瘮?shù)回調(diào)的閉包實(shí)際引用的是變量,而非變量的值。 本文旨在總結(jié)在js學(xué)習(xí)過程中,對閉包的思考,理解,以及一些反思,如有不足,還請大家指教 閉包---closure 閉包是js中比較特殊的一個(gè)概念,其特殊之處...

    HtmlCssJs 評論0 收藏0
  • JavaScript:體驗(yàn)異步的優(yōu)雅解決方案

    摘要:但是的的出現(xiàn)碉堡的新朋友,我們可以輕松寫出同步風(fēng)格的代碼同時(shí)又擁有異步機(jī)制,可以說是目前最簡單,最優(yōu)雅,最佳的解決方案了。不敢說這一定是終極的解決方案,但確實(shí)是目前最優(yōu)雅的解決方案 一、異步解決方案的進(jìn)化史 JavaScript的異步操作一直是個(gè)麻煩事,所以不斷有人提出它的各種解決方案。可以追溯到最早的回調(diào)函數(shù)(ajax老朋友),到Promise(不算新的朋友),再到ES6的Gener...

    happyfish 評論0 收藏0
  • 原創(chuàng) | JS異步工具之--Promise

    摘要:作者珂珂滬江前端開發(fā)工程師本文為原創(chuàng)文章,有不當(dāng)之處歡迎指出。只對未來發(fā)生的事情做出兩種基本情況的應(yīng)對成功和失敗。在異步轉(zhuǎn)同步這條道路上,只是一個(gè)出彩的點(diǎn),他還尚有一些缺陷和不足,并不是我們最終的解決方案。 作者:珂珂 (滬江前端開發(fā)工程師)本文為原創(chuàng)文章,有不當(dāng)之處歡迎指出。轉(zhuǎn)載請標(biāo)明出處。 一個(gè)新事物的產(chǎn)生必然是有其歷史原因的。為了更好的以同步的方式寫異步的代碼,人們在JS上操碎了...

    alanoddsoff 評論0 收藏0
  • 你不知道的JavaScript(ES6與之未來)

    摘要:然而,臨近規(guī)范發(fā)布時(shí),有建議提及未來的版本號切換為編年制,比如用同來指代在年末前被定稿的所有版本??偟脕碚f就是版本號不再那么重要了,開始變得更像一個(gè)萬古長青的活標(biāo)準(zhǔn)。 你不知道的JS(下卷)ES6與之未來 第一章:ES的今與明 在你想深入這本書之前,你應(yīng)該對(在讀此書時(shí))JavaScript的最近標(biāo)準(zhǔn)掌握熟練,也就是ES5(專業(yè)來說是ES 5.1)。在此,我們決定全方面地談?wù)撽P(guān)于將近的...

    Julylovin 評論0 收藏0
  • 半理解系列--Promise的進(jìn)化史

    摘要:異步編程一般用來調(diào)取接口拉數(shù)據(jù)。通過我描述的篇幅,就知道異步編程比同步編程麻煩許多。遠(yuǎn)古時(shí)期,異步編程是通過回調(diào)函數(shù)來解決的。 半理解系列--Promise的進(jìn)化史 學(xué)過js的都知道,程序有同步編程和異步編程之分,同步就好比流水線,一步一個(gè)腳印,做完上個(gè)任務(wù)才做下一個(gè),異步編程好比客服,客服接了一個(gè)電話,收到了一個(gè)任務(wù),然后把任務(wù)交給另外的人來處理,同時(shí),繼續(xù)接聽下一個(gè)電話,等到另外的...

    Eminjannn 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<