国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript 異步進化史

luzhuqun / 2932人閱讀

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

同步與異步

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

例 1

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

在上述代碼中,A、B、C 是三個不同的函數,每個函數都是一個不相關的任務。在同步模式下,計算機會先執行 A 任務,再執行 B 任務,最后執行 C 任務。在大部分情況,同步模式都沒問題。但是如果 B 任務是一個耗時很長網絡的請求,而 C 任務恰好是展現新頁面,B 與 C 沒有依賴關系。這就會導致網頁卡頓的現象。有一種解決方案,將 B 放在 C 后面去執行,但唯一有些不足的是,B 的網絡請求會遲一些再發送。

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

例 2

A();
// 在現在發送請求
ajax("url1",function B() {
  // 在未來某個時刻執行
})
C();
// 執行順序 A => C => B

實際上,JavaScript 引擎先執行了調用了瀏覽器的網絡請求接口的任務(一部分任務),再由瀏覽器發送網絡請求并監聽請求返回(這個任務不由 JavaScript 引擎執行,而是瀏覽器);等請求放回后,瀏覽器再通知 JavaScript 引擎,開始執行回調函數中的任務(另一部分)。JavaScript 異步能力的本質是瀏覽器或 Node 的多線程能力。

callback

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

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

例 3

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

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

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

上面的例子中,我們可以通過將 ajax 后面執行的任務 E 和 任務 D 提前,來進行代碼優化。這種技巧在寫多重嵌套的代碼時,是非常有用的。改進后,如下。

例 4

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

在例 4 中,只有處理了成功回調,并沒處理異常回調。接下來,把異常處理回調加上,再來討論代碼“線性”執行的問題。

例 5

A();

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

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

},function(){
    E();

});

例 5 中,加上異常處理回調后,url1 的成功回調函數 B 和異常回調函數 E,被分開了。這種“非線性”的情況又出現了。

在 node 中,為了解決的異常處理“非線性”的問題,制定了錯誤優先的策略。node 中 callback 的第一個參數,專門用于判斷是否發生異常。

例 6

A();

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

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

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

promise

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

Promise 就像一個中介,它承諾會將一個可信任的異步結果返回。簽訂協議的兩方分別是異步接口和 callback。首先 Promise 和異步接口簽訂一個協議,成功時,調用 resolve 函數通知 Promise,異常時,調用 reject 通知 Promise。另一方面 Promise 和 callback 也簽訂一個協議,當異步接口的 resolvereject 被調用時,由 Promise 返回可信任的值給 thencatch 中注冊的 callback。

一個最簡單的 promise 示例如下:

例 7

// 創建一個 Promise 實例(異步接口和 Promise 簽訂協議)
var promise = new Promise(function (resolve,reject) {
  ajax("url",resolve,reject);
});

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

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

例 8

var promise1 = new Promise(function (resolve) {
  // 由于某些原因導致“異步”接口,被同步執行了
  if (true ){
    // 同步代碼
    resolve("B");
  } else {
    // 異步代碼
    setTimeout(function(){
      resolve("B");
    },0)
  }

});

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

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

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

例 9

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

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

介紹完 Promise 的特性后,來看看它如何利用鏈式調用,解決 callback 模式下,異步代碼可讀性的問題。鏈式調用指的是:函數 return 一個可以繼續執行的對象,該對象可以繼續調用,并且 return 另一個可以繼續執行的對象,如此反復達到不斷調用的結果。如例 10:

例 10

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

A();
fetch("url1").then(function(){
    B();
    // 返回一個新的 Promise 實例
    return fetch("url2");
}).catch(function(){
    C();
    // 異常的時候也可以返回一個新的 Promise 實例
    return fetch("url2");
    // 使用鏈式寫法調用這個新的 Promise 實例的 then 方法
}).then(function() {
    // 可以繼續 return,也可以不繼續 return,結束鏈式調用
    D();
})
// A B C D (順序執行)

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

另外,Promise 還解決了一個難點,callback 只能捕獲當前錯誤異常。Promise 和 callback 不同,每個 callback 只能知道自己的報錯情況,但 Promise 代理著所有的 callback,所有 callback 的報錯,都可以由 Promise 統一處理。所以,可以通過在最后設置一個 catch 來捕獲之前未捕獲異常。

Promise 解決 callback 的異步調用問題,但 Promise 并沒有擺脫 callback,它只是將 callback 放到一個可以信任的中間機構,這個中間機構去鏈接 callback 和異步接口。此外,鏈式調用的寫法并不是非常優雅。接下來介紹的異步(async)函數方案,會給出一個更好的解決方案。

異步(async)函數

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

聲明異步函數,只需在普通函數前添加一個關鍵字 async 即可,如:

async function main(){}

在異步函數中,可以使用 await 關鍵字,表示等待后面表達式的執行結果,再往下繼續執行。表達式一般都是 Promise 實例。如,例 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);
    // 不會立刻執行,等待 100ms 后才開始執行
    console.log(value);  // done
}

main();

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

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

在異步函數中,使用 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);

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

例 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

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

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

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

例 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();

但這樣會導致 url2 的請求必需等到 url1 的請求回來后才會發送。如果 url1url2 沒有相互的依賴關系,將這兩個請求同時發送實現的效果會更好。

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

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();

最后對異步函數的內容做個小結:

聲明: async function main(){}

異步函數邏輯:可以使用 await

調用: main()

捕獲異常: try...catch

傳入參數: main("第一個參數")

return:不生效

異步函數作為參數傳入其他函數:可以

處理并發邏輯:Promise.all

目前使用最新的 Chrome/node 已經支持 ES7 異步函數的寫法了,另外也可以通過 Babel 以將異步函數轉義為 ES5 的語法執行。大家可以自己動手試試,使用異步函數,用類同步的方式,書寫異步代碼。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90840.html

相關文章

  • javascript 閉包 ---- js進化之路

    摘要:閉包利用的,其實就是作用域嵌套情況下,內部作用域可以訪問外部作用域這一特性。之所以要將閉包和垃圾回收策略聯系在一起,是因為這涉及到閉包的一些重要特性,如變量暫時存儲和內存泄漏。因為匿名函數回調的閉包實際引用的是變量,而非變量的值。 本文旨在總結在js學習過程中,對閉包的思考,理解,以及一些反思,如有不足,還請大家指教 閉包---closure 閉包是js中比較特殊的一個概念,其特殊之處...

    HtmlCssJs 評論0 收藏0
  • JavaScript:體驗異步的優雅解決方案

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

    happyfish 評論0 收藏0
  • 原創 | JS異步工具之--Promise

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

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

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

    Julylovin 評論0 收藏0
  • 半理解系列--Promise的化史

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

    Eminjannn 評論0 收藏0

發表評論

0條評論

luzhuqun

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<