搜尋
首頁web前端js教程JavaScript 非同步進化史的程式碼實例詳細介紹

前言

JS 中最基礎的非同步呼叫方式是callback,它將回呼函數callback 傳給非同步API,由瀏覽器或Node 在非同步完成後,通知JS 引擎調用callback。對於簡單的非同步操作,用 callback 實現,是夠用的。但隨著負責互動頁面和 Node 出現,callback 方案的弊端開始浮現。 Promise 規範孕育而生,並納入 ES6 的規範中。後來 ES7 又在 Promise 的基礎上將 async 函數納入標準。此為 JavaScript 非同步進化史。

同步與非同步

通常,程式碼是由上往下依序執行的。如果有多個任務,就必須排隊,前一個任務完成,後一個任務才會執行。這種執行模式稱之為:同步(synchronous)。新手容易把計算機用語中的同步,和日常用語中的同步混淆。如,「把檔案同步到雲端」中的同步,指的是「使…保持一致」。而在計算機中,同步指的是任務從上往下依序執行的模式。例如:

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

在這段程式碼中,A、B、C是三個不同的函數,每個函數都是一個不相關的任務。在同步模式,電腦會先執行 A 任務,再執行 B 任務,最後執行 C 任務。在大部分情況,同步模式都沒問題。但如果 B 任務是耗時很長的網路請求,而 C 任務恰好是展現新頁面,就會導致網頁卡頓。

更好解決方案是,將 B 任務分成兩個部分。一部分立即執行網路請求的任務,另一部分在請求回來後的執行任務。這種一部分立即執行,另一部分在未來執行的模式稱為非同步。

A();
// 在现在发送请求 
ajax('url1',function B() {
  // 在未来某个时刻执行
})
C();
// 执行顺序 A => C => B

實際上,JS 引擎並沒有直接處理網路請求的任務,它只是調用了瀏覽器的網路請求接口,由瀏覽器發送網路請求並監聽返回的資料。 JavaScript 非同步能力的本質是瀏覽器或 Node 的多執行緒能力。

callback

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

試著判斷下面這段程式碼的執行順序,你會對「非線性」程式碼比「線性」程式碼更難閱讀,體會更深。

A();

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

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

});
E();
// A => E => B => D => C

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

透過將ajax後面執行的任務提前,可以更容易看懂程式碼的執行順序。雖然程式碼因為嵌套看起來不美觀,但現在的執行順序卻是從上到下的「線性」方式。這種技巧在寫多重巢狀的程式碼時,是非常有用的。

A();
E();

ajax('url1', function(){
    B();
    D();

    ajax('url2', function(){
        C();
    }

});
// A => E => B => D => C

上一段程式碼只有處理了成功回調,並沒有處理異常回呼。接下來,把異常處理回呼加上,再來討論程式碼「線性」執行的問題。

A();

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

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

},function(){
    E();

});

加上異常處理回呼後,url1的成功回呼函數 B 和異常回呼函數 E,被分開了。這種「非線性」的情況又出現了。

在 node 中,為了解決的異常回呼導致的「非線性」的問題,制定了錯誤優先的策略。 node 中 callback 的第一個參數,專門用來判斷是否發生異常。

A();

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

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

到此,callback 引起的「非線性」問題基本上得到解決。遺憾的是,使用 callback 嵌套,一層層if else和回呼函數,一旦嵌套層數多起來,閱讀起來不是很方便。此外,callback 一旦出現異常,只能在目前回呼函數內部處理異常。

promise

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

Promise 就像一個中介,它承諾會將一個可信任的非同步結果回傳。首先 Promise 和非同步介面簽訂一個協議,成功時,呼叫resolve函數通知 Promise,異常時,呼叫reject通知 Promise。另一方面 Promise 和 callback 也簽訂一個協議,由 Promise 在將來返回可信任的值給thencatch中註冊的 callback。

// 创建一个 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 一定会被异步调用,且只会被调用一次。

var promise1 = new Promise(function (resolve) {
  // 可能由于某些原因导致同步调用
  resolve('B');
});
// promise依旧会异步执行
promise1.then(function(value){
    console.log(value)
});
console.log('A');
// A B (先 A 后 B)

var promise2 = new Promise(function (resolve) {
  // 成功回调被通知了2次
  setTimeout(function(){
    resolve();
  },0)
});
// promise只会调用一次
promise2.then(function(){
    console.log('A')
});
// A (只有一个)

var promise3 = new Promise(function (resolve,reject) {
  // 成功回调先被通知,又通知了失败回调
  setTimeout(function(){
    resolve();
    reject();
  },0)

});
// promise只会调用成功回调
promise3.then(function(){
    console.log('A')
}).catch(function(){
    console.log('B')
});
// A(只有A)

介绍完 Promise 的特性后,来看看它如何利用链式调用,解决异步代码可读性的问题的。

var fetch = function(url){
    // 返回一个新的 Promise 实例
    return new Promise(function (resolve,reject) {
        ajax(url,resolve,reject);
    });
}

A();
fetch('url1').then(function(){
    B();
    // 返回一个新的 Promise 实例
    return fetch('url2');
}).catch(function(){
    // 异常的时候也可以返回一个新的 Promise 实例
    return fetch('url2');
    // 使用链式写法调用这个新的 Promise 实例的 then 方法    
}).then(function() {
    C();
    // 继续返回一个新的 Promise 实例...
})
// A B C ...

如此反复,不断返回一个 Promise 对象,再采用链式调用的方式不断地调用。使 Promise 摆脱了 callback 层层嵌套的问题和异步代码“非线性”执行的问题。

Promise 解决的另外一个难点是 callback 只能捕获当前错误异常。Promise 和 callback 不同,每个 callback 只能知道自己的报错情况,但 Promise 代理着所有的 callback,所有 callback 的报错,都可以由 Promise 统一处理。所以,可以通过catch来捕获之前未捕获的异常。

Promise 解决了 callback 的异步调用问题,但 Promise 并没有摆脱 callback,它只是将 callback 放到一个可以信任的中间机构,这个中间机构去链接我们的代码和异步接口。

异步(async)函数

异步(async)函数是 ES7 的一个新的特性,它结合了 Promise,让我们摆脱 callback 的束缚,直接用类同步的“线性”方式,写异步函数。

声明异步函数,只需在普通函数前添加一个关键字 async 即可,如async function main(){} 。在异步函数中,可以使用await关键字,表示等待后面表达式的执行结果,一般后面的表达式是 Promise 实例。

async function main{
    // timer 是在上一个例子中定义的
    var value = await timer(100);
    console.log(value); // done (100ms 后返回 done)
}

main();

异步函数和普通函数一样调用 main() 。调用后,会立即执行异步函数中的第一行代码 var value = await timer(100) 。等到异步执行完成后,才会执行下一行代码。

除此之外,异步函数和其他函数基本类似,它使用try...catch来捕捉异常。也可以传入参数。但不要在异步函数中使用return来返回值。

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

async function main(delay){
  try{
    var value1 = await timer(delay);
    var value2 = await timer('');
    var value3 = await timer(delay);
  }catch(err){
    console.error(err);
      // Error: type error
      //   at create (<anonymous>:5:14)
      //   at timer (<anonymous>:3:10)
      //   at A (<anonymous>:12:10)
  }
}
main(0);

异步函数也可以被当作值,传入普通函数和异步函数中执行。但是在异步函数中,使用异步函数时要注意,如果不使用await,异步函数会被同步执行。

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

async function doAsync(main){
  main(0);
  console.log(&#39;B&#39;)
}

doAsync(main);
// B A

这个时候打印出来的值是 B A。说明 doAsync 函数并没有等待 main 的异步执行完毕就执行了 console。如果要让 console 在main 的异步执行完毕后才执行,我们需要在main前添加关键字await

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

async function doAsync(main){
    await main(0);
    console.log(&#39;B&#39;)
}

doAsync(main);
// A B

由于异步函数采用类同步的书写方法,所以在处理多个并发请求,新手可能会像下面一样书写。这样会导致url2的请求必需等到url1的请求回来后才会发送。

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

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

main();

使用Promise.all的方法来解决这个问题。Promise.all用于将多个Promise实例,包装成一个新的 Promis e实例,当所有的 Promise 成功后才会触发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(&#39;url1&#39;),fetch(&#39;url2&#39;)];
    conosle.log(arrValue[0],arrValue[1]);
  }catch(err){
    console.error(err)
  }
}

main();

目前使用 Babel 已经支持 ES7 异步函数的转码了,大家可以在自己的项目中开始尝试。

以上就是JavaScript 异步进化史的代码实例详细介绍的内容,更多相关内容请关注PHP中文网(www.php.cn)!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
C和JavaScript:連接解釋C和JavaScript:連接解釋Apr 23, 2025 am 12:07 AM

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。