JS實(shí)現(xiàn)網(wǎng)絡(luò)請求的三種方式梳理
背景
為了應(yīng)對越來越多的測試需求,減少重復(fù)性的工作,有道智能硬件測試組基于 electron 開發(fā)了一系列測試提效工具。
隨著工具的快速開發(fā)迭代,代碼中出現(xiàn)了越來越多的嵌套的回調(diào)函數(shù),工具崩潰的幾率也越來越大。為了解決這些問題, 我們用 async/await 對這些回調(diào)函數(shù)進(jìn)行了重構(gòu), 使得代碼量下降,代碼的可讀性和可理解性都有了大幅度提高。
本文介紹了 基于 XMLHttpRequest、Promise、async/await 等三種異步網(wǎng)絡(luò)請求 的寫法,其中 async/await 寫法允許我們以類似于同步的方式編寫異步程序,擺脫繁瑣的回調(diào)函數(shù)。
前言
在 js 中如果只是發(fā)起單個(gè)網(wǎng)絡(luò)請求還不算復(fù)雜,用fetch、axios或者直接用XMLHttpRequest就能滿足要求。
但若是多個(gè)請求按順序拉取數(shù)據(jù)那寫起來就很麻煩了,因?yàn)?js 中的網(wǎng)絡(luò)請求都是異步的,想要順序執(zhí)行, 最常見寫法就是在回調(diào)函數(shù)中發(fā)起下一個(gè)請求 ,如下面這些代碼:
const requestOptions = { method: 'GET', redirect: 'follow' }; fetch('https://xxx.yyy.com/api/zzz/', requestOptions) .then(response => response.json()) .then(data => { fetch('https://xxx.yyy.com/api/aaa/'+data.id, requestOptions) .then(response => response.json()) .then(data => { console.log(data) }) .catch(error => console.error('error', error)); }) .catch(error => console.error('error', error));
假設(shè)我需要經(jīng)過兩步獲取一個(gè)數(shù)據(jù),如從 https://xxx.yyy.com/api/zzz/ 獲取一個(gè)數(shù)據(jù)對象data,通過 data.id 得到我要獲取數(shù)據(jù)的序號(hào),之后再發(fā)一次請求得到想要的數(shù)據(jù)。
用回調(diào)函數(shù)的方式就類似于上面這樣,太繁瑣了,而且容易出錯(cuò),并且一旦邏輯復(fù)雜就不好改。
接下來梳理一下js的幾種網(wǎng)絡(luò)請求方式,擺脫回調(diào)地獄,希望對遇到類似問題的小伙伴有所幫助。
XMLHttpRequest
首先是 XMLHttpRequest,初學(xué)前端時(shí)大名鼎鼎的 Ajax 主要指的就是它。通過 XMLHttpRequest 對象創(chuàng)建網(wǎng)絡(luò)請求的套路如下:
// 假設(shè)訪問http://localhost:3000/user返回json對象{"name":"YouDao"} const xhr = new XMLHttpRequest(); const url = 'http://localhost:3000/user' xhr.onreadystatechange = function(){ if (this.readyState == 4 && this.status == 200){ const json=JSON.parse(xhr.responseText) const name=json.name console.log(name) } } xhr.open('GET',url) xhr.send()
這段代碼首先創(chuàng)建一個(gè) XMLHttpRequest 對象 xhr,然后給 xhr.onreadystatechange 添加 readystatechange 事件的回調(diào)函數(shù),之后 xhr.open('GET',url) 初始化請求,最后由xhr.send() 發(fā)送請求。
請求發(fā)送后,程序會(huì)繼續(xù)執(zhí)行不會(huì)阻塞,這也是異步調(diào)用的好處。當(dāng)瀏覽器收到響應(yīng)時(shí)就會(huì)進(jìn)入xhr.onreadystatechange 的回調(diào)函數(shù)中去。在整個(gè)請求過程中, xhr.onreadystatechange會(huì)觸發(fā)四次,每次 readyState 都會(huì)自增,從1一直到4,只有到了最后階段也就是readyState為4時(shí)才能得到最終的響應(yīng)數(shù)據(jù)。
到達(dá)第四階段后還要根據(jù) status 判斷響應(yīng)的狀態(tài)碼是否正常,通常響應(yīng)碼為200說明請求沒有遇到問題。這段代碼最終會(huì)在控制臺(tái)上會(huì)打出 YouDao。
可以看出,通過XMLHttpRequest處理請求的話,首先要針對每個(gè)請求創(chuàng)建一個(gè) XMLHttpRequest 對象,然后還要對每個(gè)對象綁定 readystatechange 事件的回調(diào)函數(shù),若是多個(gè)請求串起來,想想就很麻煩。
Promise
Promise 是在 ECMAScript 2015 引入的,如果一個(gè)事件依賴于另一個(gè)事件返回的結(jié)果,那么使用回調(diào)會(huì)使代碼變得很復(fù)雜。
Promise 對象提供了檢查操作失敗或成功的一種模式。如果成功,則會(huì)返回另一個(gè)Promise。這使得回調(diào)的書寫更加規(guī)范。
通過 Promise 處理的套路如下:
const promise = new Promise((resolve,reject)=>{ let condition = true; if (condition) { resolve("ok") } else { reject("failed") } }).then( msg => console.log(msg)) .catch( err => console.error(err)) .finally( _ =>console.log("finally"))
上面這段代碼把整個(gè)處理過程串起來了,首先創(chuàng)建一個(gè) Promise 對象,它的構(gòu)造器接收一個(gè)函數(shù),函數(shù)的第一個(gè)參數(shù)是沒出錯(cuò)時(shí)要執(zhí)行的函數(shù) resolve,第二個(gè)參數(shù)是出錯(cuò)后要執(zhí)行的函數(shù)r eject。
resolve 指執(zhí)行成功后then里面的回調(diào)函數(shù),reject 指執(zhí)行失敗后catch里執(zhí)行的回調(diào)函數(shù)。最后的 finally 是不論成功失敗都會(huì)執(zhí)行的,可以用來做一些收尾清理工作。
基于 Promise 的網(wǎng)絡(luò)請求可以用 axios 庫或?yàn)g覽器自帶的 fetch 實(shí)現(xiàn)。
axios 庫創(chuàng)建請求的套路如下:
import axios from 'axios' const url = 'http://xxx.yyy.com/' axios.get(url) .then(data => console.log(data)) .catch(err => console.error(err))
我比較喜歡用 fetch,fetch 是用來代替 XMLHttpRequest 的瀏覽器 API,它不需要導(dǎo)庫,fetch 創(chuàng)建請求的方式和axios類似,在開頭已經(jīng)展示過了就不重復(fù)寫了。
雖然 Promise 把回調(diào)函數(shù)的編寫方式簡化了一些,但還是沒有擺脫回調(diào)地獄,多個(gè)請求串起來的話就會(huì)像我開頭寫的那樣,在 then 里面創(chuàng)建新的 Promise,最終變成 Promise 地獄。
async/await
async/await 是在 ECMAScript 2017 引入的,可以簡化 Promise 的寫法,使得代碼中的異步函數(shù)調(diào)用可以按順序執(zhí)行,易于理解。
下面就用開頭的那個(gè)例子說明吧:
直接用 fetch 獲取數(shù)據(jù):
const requestOptions = { method: 'GET', redirect: 'follow' }; fetch('https://xxx.yyy.com/api/zzz/', requestOptions) .then(response => response.json()) .then(data => { fetch('https://xxx.yyy.com/api/aaa/'+data.id, requestOptions) .then(response => response.json()) .then(data => { console.log(data) }) .catch(error => console.error('error', error)); }) .catch(error => console.error('error', error));
用async/await改寫后:
async function demo() { const requestOptions = { method: 'GET', redirect: 'follow' }; const response = await fetch('https://xxx.yyy.com/api/zzz/', requestOptions); const data = await response.json() const response1 = await fetch('https://xxx.yyy.com/api/aaa/'+data.id, requestOptions) const data1 = await response1.json() console.log(data1) } demo().catch(error => console.error('error',error))
改寫后的代碼是不是就很清楚了,沒有那么多的 then 跟在后面了,這樣如果有一連串的網(wǎng)絡(luò)請求也不用怕了。
當(dāng) async 放在一個(gè)函數(shù)的聲明前時(shí),這個(gè)函數(shù)就是一個(gè)異步函數(shù),調(diào)用該函數(shù)會(huì)返回一個(gè)Promise。
await 用于等待一個(gè) Promise 對象,它只能在異步函數(shù)中使用,await 表達(dá)式會(huì)暫停當(dāng)前異步函數(shù)的執(zhí)行,等待 Promise 處理完成。
這樣如果想讓一連串的異步函數(shù)調(diào)用順序執(zhí)行,只要把被調(diào)用的這些函數(shù)放到一個(gè)用async修飾的函數(shù)中,調(diào)用前加上 await 就能讓這些函數(shù)乖乖地順序執(zhí)行了。
結(jié)語
通過本文的梳理,相信你已經(jīng)知道怎樣避免回調(diào)地獄了。不過需要注意的是 Promise 是2015年加入語言規(guī)范的,而 async/await 是2017年才加入到語言規(guī)范的,如果你的項(xiàng)目比較老或者是必須要兼容老版本的瀏覽器(如IE6),那就需要用別的方式來解決回調(diào)地獄了。
對于 electron 只要你用的是近幾年的版本都是支持的,electron 可以當(dāng)成是 chromium 和 node.js 的結(jié)合體,特別適合用來寫跨平臺(tái)的工具類桌面應(yīng)用程序。
到此這篇關(guān)于JS實(shí)現(xiàn)網(wǎng)絡(luò)請求的三種方式梳理的文章就介紹到這了,更多相關(guān)JS網(wǎng)絡(luò)請求內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談javascript控制HTML5的全屏操控,瀏覽器兼容的問題
下面小編就為大家?guī)硪黄獪\談javascript控制HTML5的全屏操控,瀏覽器兼容的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10完美解決input[type=number]無法顯示非數(shù)字字符的問題
下面小編就為大家?guī)硪黄昝澜鉀Qinput[type=number]無法顯示非數(shù)字字符的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02js中實(shí)現(xiàn)多態(tài)采用和繼承類似的方法
首先定義一個(gè)抽象類,其中調(diào)用一些虛方法,虛方法在抽象類中沒用定義,而是通過其具體的實(shí)現(xiàn)類來實(shí)現(xiàn)2014-08-08微信小程序 子級(jí)頁面返回父級(jí)并把子級(jí)參數(shù)帶回父級(jí)實(shí)現(xiàn)方法
這篇文章主要介紹了微信小程序 子級(jí)頁面返回父級(jí)并把子級(jí)參數(shù)帶回父級(jí)實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08javascript中的數(shù)據(jù)類型檢測方法詳解
這篇文章主要介紹了javascript中的數(shù)據(jù)類型檢測方法,結(jié)合實(shí)例形式分析了javascript數(shù)據(jù)類型并總結(jié)分析了常見的數(shù)據(jù)類型檢測操作技巧,需要的朋友可以參考下2019-08-08純JavaScript代碼實(shí)現(xiàn)移動(dòng)設(shè)備繪圖解鎖
為了個(gè)人信息的安全起見,移動(dòng)設(shè)備上都有個(gè)繪圖解鎖,使用起來非常簡單,代碼是怎么實(shí)現(xiàn)的呢?下面小編給大家介紹js實(shí)現(xiàn)移動(dòng)設(shè)備繪圖解鎖,需要的朋友可以參考下2015-10-10