這個(gè)API很“迷人”——Fetch API

JavaScript 通過XMLHttpRequest(XHR)來執(zhí)行異步請(qǐng)求滥酥,這個(gè)方式已經(jīng)存在了很長(zhǎng)一段時(shí)間养篓。雖說它很有用而钞,但它不是最佳API颖医。它在設(shè)計(jì)上不符合職責(zé)分離原則茉继,將輸入轧葛、輸出和用事件來跟蹤的狀態(tài)混雜在一個(gè)對(duì)象里纳本。而且苔可,基于事件的模型與最近JavaScript流行的Promise以及基于生成器的異步編程模型不太搭(事件模型在處理異步上有點(diǎn)過時(shí)了——譯者注)既绕。

新的FetchAPI打算修正上面提到的那些缺陷啄刹。 它向JS中引入和HTTP協(xié)議中同樣的原語(即Fetch——譯者注)。具體而言凄贩,它引入一個(gè)實(shí)用的函數(shù)fetch()用來簡(jiǎn)潔捕捉從網(wǎng)絡(luò)上檢索一個(gè)資源的意圖誓军。

Fetch 規(guī)范的API明確了用戶代理獲取資源的語義。它結(jié)合ServiceWorkers疲扎,嘗試達(dá)到以下優(yōu)化:

改善離線體驗(yàn)

保持可擴(kuò)展性

到寫這篇文章的時(shí)候昵时,F(xiàn)etch API被Firefox 39(Nightly版)以及Chrome 42(開發(fā)版)支持。在github上椒丧,有基于低版本瀏覽器的兼容實(shí)現(xiàn)

特性檢測(cè)

要檢查是否支持Fetch API壹甥,可以通過檢查 Headers, Request, Response 或者 fetch 在 window 或者 worker 作用域中是否存在。

簡(jiǎn)單的fetching示例

在Fetch API中壶熏,最常用的就是fetch()函數(shù)句柠。它接收一個(gè)URL參數(shù),返回一個(gè)promise來處理response。response參數(shù)帶著一個(gè)Response對(duì)象溯职。

fetch("/data.json").then(function(res){// res instanceof Response == true.if(res.ok){res.json().then(function(data){console.log(data.entries);});}else{console.log("Looks like the response wasn't perfect, got status",res.status);}},function(e){console.log("Fetch failed!",e);});

如果是提交一個(gè)POST請(qǐng)求管怠,代碼如下:

fetch("http://www.example.org/submit.php",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"firstName=Nikhil&favColor=blue&password=easytoguess"}).then(function(res){if(res.ok){alert("Perfect! Your settings are saved.");}elseif(res.status==401){alert("Oops! You are not authorized.");}},function(e){alert("Error submitting form!");});

fetch()函數(shù)的參數(shù)和傳給Request()構(gòu)造函數(shù)的參數(shù)保持完全一致,所以你可以直接傳任意復(fù)雜的request請(qǐng)求給fetch()缸榄。

Headers

Fetch引入了3個(gè)接口,它們分別是 Headers,Request 以及 Response 祝拯。他們直接對(duì)應(yīng)了相應(yīng)的HTTP概念甚带,但是基于安全考慮,有些區(qū)別佳头,例如支持CORS規(guī)則以及保證cookies不能被第三方獲取鹰贵。

Headers接口是一個(gè)簡(jiǎn)單的多映射的名-值表

varcontent="Hello World";varreqHeaders=newHeaders();reqHeaders.append("Content-Type","text/plain");reqHeaders.append("Content-Length",content.length.toString());reqHeaders.append("X-Custom-Header","ProcessThisImmediately");

也可以傳一個(gè)多維數(shù)組或者json:

reqHeaders=newHeaders({"Content-Type":"text/plain","Content-Length":content.length.toString(),"X-Custom-Header":"ProcessThisImmediately",});

Headers的內(nèi)容可以被檢索:

console.log(reqHeaders.has("Content-Type"));// trueconsole.log(reqHeaders.has("Set-Cookie"));// falsereqHeaders.set("Content-Type","text/html");reqHeaders.append("X-Custom-Header","AnotherValue");console.log(reqHeaders.get("Content-Length"));// 11console.log(reqHeaders.getAll("X-Custom-Header"));// ["ProcessThisImmediately", "AnotherValue"]reqHeaders.delete("X-Custom-Header");console.log(reqHeaders.getAll("X-Custom-Header"));// []

一些操作不僅僅對(duì)ServiceWorkers有用,本身也提供了更方便的操作Headers的API(相對(duì)于XMLHttpRequest來說——譯者注)康嘉。

由于Headers可以在request請(qǐng)求中被發(fā)送或者在response請(qǐng)求中被接收碉输,并且規(guī)定了哪些參數(shù)是可寫的,Headers對(duì)象有一個(gè)特殊的guard屬性亭珍。這個(gè)屬性沒有暴露給Web敷钾,但是它影響到哪些內(nèi)容可以在Headers對(duì)象中被改變。

可能的值如下:

“none”: 默認(rèn)的

“request”: 從Request中獲得的Headers只讀肄梨。

“request-no-cors”:從不同域的Request中獲得的Headers只讀阻荒。

“response”: 從Response獲得的Headers只讀。

“immutable” 在ServiceWorkers中最常用的众羡,所有的Headers都只讀侨赡。

哪一種 guard 作用于 Headers 導(dǎo)致什么行為,詳細(xì)定義在了這個(gè)規(guī)范中粱侣。例如羊壹,你不可以添加或者修改一個(gè)guard屬性是”request”的Request Headers的”Content-Length”屬性。同樣地齐婴,插入”Set-Cookie”屬性到一個(gè)Response headers是不允許的油猫,因此ServiceWorkers是不能給合成的Response的headers添加一些cookies。

如果使用了一個(gè)不合法的HTTP Header屬性名尔店,那么Headers的方法通常都拋出 TypeError 異常眨攘。如果不小心寫入了一個(gè)不可寫的屬性,也會(huì)拋出一個(gè) TypeError 異常嚣州。除此以外的情況鲫售,失敗了并不拋出異常。例如:

varres=Response.error();try{res.headers.set("Origin","http://mybank.com");}catch(e){console.log("Cannot pretend to be a bank!");}

Request

Request接口定義了通過HTTP請(qǐng)求資源的request格式该肴。參數(shù)需要URL情竹、method和headers,同時(shí)Request也接受一個(gè)特定的body匀哄,mode秦效,credentials以及cache hints.

最簡(jiǎn)單的 Request 當(dāng)然是一個(gè)URL雏蛮,可以通過URL來GET一個(gè)資源。

varreq=newRequest("/index.html");console.log(req.method);// "GET"console.log(req.url);// "http://example.com/index.html"

你也可以將一個(gè)建好的Request對(duì)象傳給構(gòu)造函數(shù)阱州,這樣將復(fù)制出一個(gè)新的Request挑秉。

varcopy=newRequest(req);console.log(copy.method);// "GET"console.log(copy.url);// "http://example.com/index.html"

這種用法通常見于ServiceWorkers。

URL以外的其他屬性的初始值能夠通過第二個(gè)參數(shù)傳給Request構(gòu)造函數(shù)苔货。這個(gè)參數(shù)是一個(gè)json:

varuploadReq=newRequest("/uploadImage",{method:"POST",headers:{"Content-Type":"image/png",},body:"image data"});

mode屬性用來決定是否允許跨域請(qǐng)求犀概,以及哪些response屬性可讀∫共眩可選的mode屬性值為same-origin姻灶,no-cors(默認(rèn))以及cors。

same-origin模式很簡(jiǎn)單诈茧,如果一個(gè)請(qǐng)求是跨域的产喉,那么返回一個(gè)簡(jiǎn)單的error,這樣確保所有的請(qǐng)求遵守同源策略敢会。

vararbitraryUrl=document.getElementById("url-input").value;fetch(arbitraryUrl,{mode:"same-origin"}).then(function(res){console.log("Response succeeded?",res.ok);},function(e){console.log("Please enter a same-origin URL!");});

no-cors模式允許來自CDN的腳本曾沈、其他域的圖片和其他一些跨域資源,但是首先有個(gè)前提條件走触,就是請(qǐng)求的method只能是”HEAD”,”GET”或者”POST”晦譬。此外,任何 ServiceWorkers 攔截了這些請(qǐng)求互广,它不能隨意添加或者改寫任何headers敛腌,除了這些。第三惫皱,JavaScript不能訪問Response中的任何屬性像樊,這保證了 ServiceWorkers 不會(huì)導(dǎo)致任何跨域下的安全問題而隱私信息泄漏。

cors模式我們通常用作跨域請(qǐng)求來從第三方提供的API獲取數(shù)據(jù)旅敷。這個(gè)模式遵守CORS協(xié)議生棍。只有有限的一些headers被暴露給Response對(duì)象,但是body是可讀的媳谁。例如涂滴,你可以獲得一個(gè)Flickr的最感興趣的照片的清單:

varu=newURLSearchParams();u.append('method','flickr.interestingness.getList');u.append('api_key','');u.append('format','json');u.append('nojsoncallback','1');varapiCall=fetch('https://api.flickr.com/services/rest?'+u);apiCall.then(function(response){returnresponse.json().then(function(json){// photo is a list of photos.returnjson.photos.photo;});}).then(function(photos){photos.forEach(function(photo){console.log(photo.title);});});

你無法從Headers中讀取”Date”屬性,因?yàn)镕lickr在Access-Control-Expose-Headers中設(shè)置了不允許讀取它晴音。

response.headers.get("Date");// null

credentials枚舉屬性決定了cookies是否能跨域得到柔纵。這個(gè)屬性與XHR的withCredentials標(biāo)志相同,但是只有三個(gè)值锤躁,分別是”omit”(默認(rèn)),”same-origin”以及”include”搁料。

Request對(duì)象也可以提供 caching hints 給用戶代理。這個(gè)屬性還在安全復(fù)審階段。Firefox 提供了這個(gè)屬性郭计,但是它目前還不起作用霸琴。

Request還有兩個(gè)只讀的屬性與ServiceWorks攔截有關(guān)。其中一個(gè)是referrer昭伸,表示Request的來源梧乘,可能為空。另外一個(gè)是context庐杨,是一個(gè)非常大的枚舉集合定義了獲得的資源的種類宋下,它可能是image比如請(qǐng)求來自于img標(biāo)簽,可能是worker如果是一個(gè)worker腳本辑莫,等等。如果使用fetch()函數(shù)罩引,這個(gè)值是fetch各吨。

Response

Response實(shí)例通常在fetch()的回調(diào)中獲得。但是它們也可以用JS構(gòu)造袁铐,不過通常這招只用于ServiceWorkers揭蜒。

Response中最常見的成員是status(一個(gè)整數(shù)默認(rèn)值是200)和statusText(默認(rèn)值是”O(jiān)K”),對(duì)應(yīng)HTTP請(qǐng)求的status和reason剔桨。還有一個(gè)”ok”屬性屉更,當(dāng)status為2xx的時(shí)候它是true。

headers 屬性是Response的Headers對(duì)象洒缀,它是只讀的(with guard “response”)瑰谜,url屬性是當(dāng)前Response的來源URL。

Response 也有一個(gè)type屬性树绩,它的值可能是”basic”,”cors”,”default”,”error”或者”opaque萨脑。

“basic”: 正常的,同域的請(qǐng)求饺饭,包含所有的headers除開”Set-Cookie”和”Set-Cookie2″渤早。

“cors”: Response從一個(gè)合法的跨域請(qǐng)求獲得,一部分header和body可讀瘫俊。

“error”: 網(wǎng)絡(luò)錯(cuò)誤鹊杖。Response的status是0,Headers是空的并且不可寫扛芽。當(dāng)Response是從Response.error()中得到時(shí)骂蓖,就是這種類型。

“opaque”: Response從”no-cors”請(qǐng)求了跨域資源胸哥。依靠Server端來做限制涯竟。

“error”類型會(huì)導(dǎo)致fetch()函數(shù)的Promise被reject并回調(diào)出一個(gè)TypeError。

還有一些屬性只在ServerWorker作用域下有效。以正確的方式 返回一個(gè)Response針對(duì)一個(gè)被ServiceWorkers攔截的Request庐船,可以像下面這樣寫:

addEventListener('fetch',function(event){event.respondWith(newResponse("Response body",{headers:{"Content-Type":"text/plain"}});});

如你所見银酬,Response有個(gè)接收兩個(gè)可選參數(shù)的構(gòu)造器。第一個(gè)參數(shù)是返回的body筐钟,第二個(gè)參數(shù)是一個(gè)json揩瞪,設(shè)置status、statusText以及headers篓冲。

靜態(tài)方法Response.error()簡(jiǎn)單返回一個(gè)錯(cuò)誤的請(qǐng)求李破。類似的,Response.redirect(url, status)返回一個(gè)跳轉(zhuǎn)URL的請(qǐng)求壹将。

處理body

無論Request還是Response都可能帶著body嗤攻。由于body可以是各種類型,比較復(fù)雜诽俯,所以前面我們故意先略過它妇菱,在這里單獨(dú)拿出來講解。

body可以是以下任何一種類型的實(shí)例:

ArrayBuffer

ArrayBufferView(Uint8Array and friends)

Blob/File

字符串

URLSearchParams

FormData——目前不被Gecko和Blink支持暴区,F(xiàn)irefox預(yù)計(jì)在版本39和Fetch的其他部分一起推出闯团。

此外,Request和Response都為他們的body提供了以下方法仙粱,這些方法都返回一個(gè)Promise對(duì)象房交。

arrayBuffer()

blob()

json()

text()

formData()

在使用非文本的數(shù)據(jù)方面,F(xiàn)etch API和XHR相比提供了極大的便利伐割。

可以通過傳body參數(shù)來設(shè)置Request的body:

varform=newFormData(document.getElementById('login-form'));fetch("/login",{method:"POST",body:form})

Response的第一個(gè)參數(shù)是body:

varres=newResponse(newFile(["chunk","chunk"],"archive.zip",{type:"application/zip"}));

Request和Response(通過fetch()方法)都能夠自動(dòng)識(shí)別自己的content type候味,Request還可以自動(dòng)設(shè)置”Content-Type” header,如果開發(fā)者沒有設(shè)置它的話隔心。

流和克隆

非常重要的一點(diǎn)要說明负溪,那就是Request和Response的body只能被讀取一次!它們有一個(gè)屬性叫bodyUsed济炎,讀取一次之后設(shè)置為true川抡,就不能再讀取了。

varres=newResponse("one time use");console.log(res.bodyUsed);// falseres.text().then(function(v){console.log(res.bodyUsed);// true});console.log(res.bodyUsed);// trueres.text().catch(function(e){console.log("Tried to read already consumed Response");});

這樣設(shè)計(jì)的目的是為了之后兼容基于流的API须尚,讓應(yīng)用一次消費(fèi)data崖堤,這樣就允許了JavaScript處理大文件例如視頻,并且可以支持實(shí)時(shí)壓縮和編輯耐床。

有時(shí)候密幔,我們希望多次訪問body,例如撩轰,你可能想用即將支持的Cache API去緩存Request和Response胯甩,以便于可以離線使用昧廷,Cache要求body能被再次讀取。

所以偎箫,我們?cè)撊绾巫宐ody能經(jīng)得起多次讀取呢木柬?API提供了一個(gè)clone()方法。調(diào)用這個(gè)方法可以得到一個(gè)克隆對(duì)象淹办。不過要記得眉枕,clone()必須要在讀取之前調(diào)用,也就是先clone()再讀取怜森。

addEventListener('fetch',function(evt){varsheep=newResponse("Dolly");console.log(sheep.bodyUsed);// falsevarclone=sheep.clone();console.log(clone.bodyUsed);// falseclone.text();console.log(sheep.bodyUsed);// falseconsole.log(clone.bodyUsed);// trueevt.respondWith(cache.add(sheep.clone()).then(function(e){returnsheep;});});

未來的改進(jìn)

為了支持流速挑,F(xiàn)etch最終將提供可以中斷執(zhí)行讀取資源的能力,并且提供可以得到讀取進(jìn)度的API副硅。這些能力在XHR中有姥宝,但是想要實(shí)現(xiàn)成Promise-based的Fetch API有些麻煩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恐疲,一起剝皮案震驚了整個(gè)濱河市伶授,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌流纹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件违诗,死亡現(xiàn)場(chǎng)離奇詭異漱凝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)诸迟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門茸炒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人阵苇,你說我怎么就攤上這事壁公。” “怎么了绅项?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵紊册,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我快耿,道長(zhǎng)囊陡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任掀亥,我火速辦了婚禮撞反,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搪花。我一直安慰自己遏片,他們只是感情好嘹害,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吮便,像睡著了一般笔呀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上线衫,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天凿可,我揣著相機(jī)與錄音,去河邊找鬼授账。 笑死枯跑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的白热。 我是一名探鬼主播敛助,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼屋确!你這毒婦竟也來了纳击?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤攻臀,失蹤者是張志新(化名)和其女友劉穎焕数,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刨啸,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡堡赔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了设联。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片善已。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖离例,靈堂內(nèi)的尸體忽然破棺而出换团,到底是詐尸還是另有隱情,我是刑警寧澤宫蛆,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布艘包,位于F島的核電站,受9級(jí)特大地震影響耀盗,放射性物質(zhì)發(fā)生泄漏辑甜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一袍冷、第九天 我趴在偏房一處隱蔽的房頂上張望磷醋。 院中可真熱鬧,春花似錦胡诗、人聲如沸邓线。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骇陈。三九已至震庭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間你雌,已是汗流浹背器联。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婿崭,地道東北人拨拓。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像氓栈,于是被迫代替她去往敵國(guó)和親渣磷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容