當(dāng)前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
【javascript】异步编年史,从“纯回调”到Promise
生活随笔
收集整理的這篇文章主要介紹了
【javascript】异步编年史,从“纯回调”到Promise
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
異步和分塊——程序的分塊執(zhí)行
? 一開始學(xué)習(xí)javascript的時候, 我對異步的概念一臉懵逼, 因為當(dāng)時百度了很多文章,但很多各種文章不負責(zé)任的把籠統(tǒng)的描述混雜在一起,讓我對這個 JS中的重要概念難以理解, “異步是非阻塞的”, “Ajax執(zhí)行是異步的”, "異步用來處理耗時操作"....? ? 所有人都再說這個是異步的,那個是異步的,異步會怎么怎樣,可我還是不知道:“異步到底是什么?” ? 后來我發(fā)現(xiàn),其實理解異步最主要的一點,就是記住: 我們的程序是分塊執(zhí)行的。 ? 分成兩塊, 同步執(zhí)行的湊一塊, 異步執(zhí)行的湊一塊,搞完同步,再搞異步 廢話不多說, 直接上圖: ?圖1?
圖2? ??
異步和非阻塞
?
我對異步的另外一個難以理解的點是異步/同步和阻塞/非阻塞的關(guān)系 人們常說: “異步是非阻塞的” , 但為什么異步是非阻塞的, 或者說, 異步和非阻塞又有什么關(guān)系呢 ? 非阻塞是對異步的要求, 異步是在“非阻塞”這一要求下的必然的解決方式? ? 咱們看看一個簡單的例子吧 ajax("http://XXX.", callback); doOtherThing()?
你肯定知道ajax這個函數(shù)的調(diào)用是發(fā)出請求取得一些數(shù)據(jù)回來, 這可能需要相當(dāng)長的一段時間(相比于其他同步函數(shù)的調(diào)用)
? 對啊,如果我們所有代碼都是同步的,這就意味著, 在執(zhí)行完ajax("http://XXX.", callback)這段代碼前, doOtherThing這個函數(shù)是不會執(zhí)行的,在外表看起來, 我們的程序不就“阻塞”在ajax("http://XXX.", callback);這個函數(shù)里了么? 這就是所謂的阻塞啊 ? 讓我們再想一想doOtherThing因為“同步”造成“阻塞”的話會有多少麻煩: doOtherThing()里面包含了這些東西: 這個簡略的函數(shù)代表了它你接下來頁面的所有的交互程序, 但你現(xiàn)在在ajax執(zhí)行結(jié)束前,你都沒有辦法去doOtherThing,去做接下來所有的交互程序了。 在外觀上看來, 頁面將會處于一個“完全假死”的狀態(tài)。 ? 因為我們要保證在大量ajax(或類似的耗時操作)的情況下,交互能正常進行 所以同步是不行的 因為同步是不行的, 所以這一塊的處理, 不就都是異步的嘛 ? 如果這樣還不太理解的話, 我們反方向思考一下, 假設(shè)一個有趣的烏托邦場景: 假設(shè)ajax的執(zhí)行能像一個同步執(zhí)行的foreach函數(shù)的執(zhí)行那樣迅速, javascript又何苦對它做一些異步處理呢? 就是因為它如此耗時, 所以javascript“審時度勢”, 拿出了“異步”的這一把刷子,來解決問題 ? 正因為有“非阻塞”的剛需, javascript才會對ajax等一概采用異步處理 ? “因為要非阻塞, 所以要異步”,這就是我個人對異步/同步和阻塞/非阻塞關(guān)系的理解 ? 可能你沒有注意到,回調(diào)其實是存在很多問題的 ? 沒錯,接下來的畫風(fēng)是這樣子的:?
回調(diào)存在的問題
? 回調(diào)存在的問題可概括為兩類: ?信任問題和控制反轉(zhuǎn)
可能你比較少意識到的一點是:我們是無法在主程序中掌控對回調(diào)的控制權(quán)的。 例如: ? ajax( "..", function(..){ } );?
我們對ajax的調(diào)用發(fā)生于現(xiàn)在,這在 JavaScript 主程序的直接控制之下。但ajax里的回調(diào)會延遲到將來發(fā)生,并且是在第三方(而不是我們的主程序)的控制下——在本例中就是函數(shù) ajax(..) 。這種控制權(quán)的轉(zhuǎn)移, 被叫做“控制反轉(zhuǎn)” ? 1.調(diào)用函數(shù)過早 ? 調(diào)用函數(shù)過早的最值得讓人注意的問題, 是你不小心定義了一個函數(shù),使得作為函數(shù)參數(shù)的回調(diào)可能延時調(diào)用,也可能立即調(diào)用。?? 也即你使用了一個可能同步調(diào)用, 也可能異步調(diào)用的回調(diào)。 這樣一種難以預(yù)測的回調(diào)。 ? 大多數(shù)時候,我們的函數(shù)總是同步的,或者總是異步的 例如foreach()函數(shù)總是同步的 array.foreach(x =>? console.log(x) ) console.log(array)?
雖然foreach函數(shù)的調(diào)用需要一定的時間,但array數(shù)組的輸出一定是在所有的數(shù)組元素都被輸出之后才輸出, 因為foreach是同步的 又如setTimeout總是異步的: setTimeout( () => {? console.log('我是異步的')? },0 ) console.log('我是同步的')?
有經(jīng)驗的JS老司機們一眼就能看出, 一定是輸出 ? 我是同步的 我是異步的?
而不是 ? 我是異步的 我是同步的?
但有些時候,我們?nèi)杂锌赡軙懗鲆粋€既可能同步, 又可能異步的函數(shù), 例如下面這個極簡的例子: 我試圖用這段代碼檢查一個輸入框內(nèi)輸入的賬號是否為空, 如果不為空就用它發(fā)起請求。(注:callback無論賬號是否為空都會被調(diào)用) ? // 注: 這是一個相當(dāng)烏托邦,且省略諸多內(nèi)容的函數(shù) function login (callback) {// 當(dāng)取得的賬號變量name的值為空時, 立即調(diào)用函數(shù),此時callback同步調(diào)用)if(!name) {callback();return?? // name為空時在這里結(jié)束函數(shù) ??????? }// 當(dāng)取得的賬號變量name的值不為空時, 在請求成功后調(diào)用函數(shù)(此時callback異步調(diào)用)request('post', name, callback) }?
相信各位機智的園友憑第六感就能知曉:這種函數(shù)絕B不是什么好東西。 ? 的確,這種函數(shù)的編寫是公認的需要杜絕的,在英語世界里, 這種可能同步也可能異步調(diào)用的回調(diào)以及包裹它的函數(shù), 被稱作是 “Zalgo” (一種都市傳說中的魔鬼), 而編寫這種函數(shù)的行為, 被稱作是"release Zalgo" (將Zalgo釋放了出來) ? 為什么它如此可怕? 因為函數(shù)的調(diào)用時間是不確定的,難以預(yù)料的。 我想沒有人會喜歡這樣難以掌控的代碼。 例如: var a =1 zalgoFunction () {// 這里還有很多其他代碼,使得a = 2可能被異步調(diào)用也可能被同步調(diào)用[??a = 2? ]} console.log(a)?
結(jié)果會輸出什么呢?? 如果zalgoFunction是同步的, 那么a 顯然等于2, 但如果 zalgoFunction是異步的,那么 a顯然等于1。于是, 我們陷入了無法判斷調(diào)用影響的窘境。 ? 這只是一個極為簡單的場景, 如果場景變得相當(dāng)復(fù)雜, 結(jié)果又會如何呢? ? 你可能想說: 我自己寫的函數(shù)我怎么會不知道呢? 請看下面: 1. 很多時候這個不確定的函數(shù)來源于它人之手,甚至來源于完全無法核實的第三方代碼 2. 在1的基礎(chǔ)上,我們把這種不確定的情況稍微變得夸張一些: 這個函數(shù)中傳入的回調(diào), 有99%的幾率被異步調(diào)用, 有1%的幾率被同步調(diào)用 ? 在1和2的基礎(chǔ)上, 你向一個第三方的函數(shù)傳了一個回調(diào), 然后在經(jīng)過了一系列不可描述的bug后...... ??
2.調(diào)用次數(shù)過多 ? 這里取《你不知道的javascript(中卷)》的例子給大家看一看: ? 作為一個公司的員工, 你需要開發(fā)一個網(wǎng)上商城, payWithYourMoney是你在確認購買后執(zhí)行的扣費的函數(shù), 由于公司需要對購買的數(shù)據(jù)做追蹤分析, 這里需要用到一個做數(shù)據(jù)分析的第三方公司提供的analytics對象中的purchase函數(shù)。 代碼看起來像這樣 ? analytics.purchase( purchaseData, function? () {payWithYourMoney () } );?
在這情況下,可能我們會忽略的一個事實是: 我們已經(jīng)把payWithYourMoney 的控制權(quán)完全交給了analytics.purchase函數(shù)了,這讓我們的回調(diào)“任人宰割” ? 然后上線后的一天, 數(shù)據(jù)分析公司的一個隱蔽的bug終于顯露出來, 讓其中一個原本只執(zhí)行一次的payWithYourMoney執(zhí)行了5次, 這讓那個網(wǎng)上商城的客戶極為惱怒, 并投訴了你們公司。 ? 可你們公司也很無奈, 這個時候驚奇的發(fā)現(xiàn):?? payWithYourMoney的控制完全不在自己的手里 !!!!! 后來, 為了保證只支付一次, 代碼改成了這樣: ? var analysisFlag? = true // 判斷是否已經(jīng)分析(支付)過一次了 analytics.purchase( purchaseData, function(){if (!analysisFlag) {payWithYourMoney ()analysisFlag = false} } );?
但是, 這種方式雖然巧妙, 但卻仍不夠簡潔優(yōu)雅(后文提到的Promise將改變這一點) 而且, 在回調(diào)函數(shù)的無數(shù)“痛點”中, 它只能規(guī)避掉一個, 如果你嘗試規(guī)避掉所有的“痛點”,代碼將比上面更加復(fù)雜而混亂。 ? 3.太晚調(diào)用或根本沒有調(diào)用 因為你失去了對回調(diào)的控制權(quán), 你的回調(diào)可能會出現(xiàn)預(yù)期之外的過晚調(diào)用或者不調(diào)用的情況(為了處理這個“痛點”你又將混入一些復(fù)雜的代碼邏輯) ? 4.吞掉報錯 回調(diào)內(nèi)的報錯是可能被包裹回調(diào)的外部函數(shù)捕捉而不報錯,(為了處理這個“痛點”你又又又將混入一些復(fù)雜的代碼邏輯) ? 5.回調(diào)根本沒有被調(diào)用沒辦法在復(fù)雜的異步場景中很好地表達代碼邏輯
? 哎呀這里我就不說廢話了:?在異步中如果你總是依賴回調(diào)的話,很容易就寫出大家都看不懂, 甚至自己過段時間也看不懂的代碼來, 嗯, 就這樣 ? 看個例子,下面的doA到doF都是異步的函數(shù) doA( function(){doB();doC( function(){doD();} )doE(); } ); doF();?
請問這段代碼的調(diào)用順序 ? 當(dāng)然你知道它肯定不是A -> B -> C -> D -> E,但即使你富有經(jīng)驗,一般也得花上一段時間的功夫才能把它理清楚吧。( A → B → C → D → E → F 。) ? 這并不是我們開發(fā)人員的鍋, 而是因為人腦的思維方式本來就是線性的, 而回調(diào)卻打破了這種線性的思維, 我們需要強制地拋棄我們看到的A -> B -> C -> D -> E的順序,去構(gòu)建另一套思維。 ? 所以說,異步編程中有大量回調(diào)混雜的時候, 所造成的可讀性差的問題,是回調(diào)本身的“表達方式“造成的 ? ??
? 回調(diào)的局限性僅僅如此? NO,請看下面: ? 對于一些比較常見的異步場景回調(diào)也沒辦法用足夠簡潔優(yōu)雅的方式去處理: 這些場景包括但不限于:鏈式,門和競態(tài) ? 鏈式 首先你肯定知道用回調(diào)處理大量存在鏈式的異步場景的畫風(fēng)是怎樣的 例如這樣: setTimeout(function (name) {var catList = name + ','setTimeout(function (name) {catList += name + ',';setTimeout(function (name) {catList += name + ',';setTimeout(function (name) {catList += name + ',';setTimeout(function (name) {catList += name;console.log(catList);}, 1, 'Lion');}, 1, 'Snow Leopard');}, 1, 'Lynx');}, 1, 'Jaguar');}, 1, 'Panther');?
讓人一臉蒙逼的回調(diào)函數(shù)地獄 ? 很顯然,大多數(shù)時候你嘗試這樣做,是因為 你需要通過調(diào)用第一層異步函數(shù),取得結(jié)果 然后把結(jié)果傳給第二層異步函數(shù),第二層異步函數(shù)也取得結(jié)果后 傳遞結(jié)果給第三個異步函數(shù), 。。。。。 N ? 很顯然,我們的代碼風(fēng)格應(yīng)該是“鏈式”風(fēng)格, 但卻因為回調(diào)的原因被硬生生折騰成了難懂的“嵌套”風(fēng)格! (別擔(dān)心, 我下面介紹的Promise將改變這一點) ? 門 什么叫“門”?, 你可以大概理解成: 現(xiàn)在有一群人準備進屋,但只有他們所有人都到齊了,才能“進門” ,也就是: 只有所有的異步操作都完成了, 我們才認為它整體完成了,才能進行下一步操作 ? 下面這個例子里, 我們試圖通過兩個異步請求操作,分別取得a和b的值并將它們以 a + b的形式 (前提: 我們希望當(dāng)a和b的取值都到達的時候才輸出!!) var a, b; function foo(x) {a = x * 2;if (a && b) {baz();} } function bar(y) {b = y * 2;if (a && b) {baz();} } function baz() {console.log( a + b ); } // ajax(..)是某個庫中的某個Ajax函數(shù) ajax( "http://some.url.1", foo ); ajax( "http://some.url.2", bar );?
這段代碼比前面那段“鏈式”里的回調(diào)地獄好懂多了,但是卻依然存在這一些問題: ? 我們使用了兩個? if (a && b) { }? 去分別保證baz是在a和b都到達后才執(zhí)行的,試著思考一下: 兩個? if (a && b) { }? 的判斷條件是否可以合并到一起呢,因為這兩個判斷條件都試圖表達同一種語意: a 和 b都到達, 能合并成一條語句的話豈不是更加簡潔優(yōu)雅 ? (一切都在為Promise做鋪墊哦~~~~啦啦啦) ? 競態(tài)(可能跟你一般理解的競態(tài)有些不同) 一組異步操作,其中一個完成了, 這組異步操作便算是整體完成了 ? 在下面,我們希望通過異步請求的方式,取得x的值,然后執(zhí)行foo或者bar,但希望只把foo或者bar其中一個函數(shù)執(zhí)行一次 ? var flag = true; function foo(x) {if (flag) {x = x + 1baz(x);flag = false} } function bar(x) {if (flag) {x = x*2baz(x);flag = false} } function baz( x ) {console.log( x ); } // ajax(..)是某個庫中的某個Ajax函數(shù) ajax( "http://some.url.1", foo ); ajax( "http://some.url.2", bar );?
在這里,我們設(shè)置了一個flag, 設(shè)它的初始值為true, 這時候foo或者bar在第一次執(zhí)行的時候, 是可以進入if內(nèi)部的代碼塊并且執(zhí)行baz函數(shù)的, 但在if內(nèi)部的代碼塊結(jié)束的時候, 我們把flag的值置為false,這個時候下一個函數(shù)就無法進入代碼塊執(zhí)行了, 這就是回調(diào)對于競態(tài)的處理 ? 正因為回調(diào)給我們帶來的麻煩很多,ES6引入了Promise的機制: ? ?一步一步地揭開Promise神秘的面紗
? 首先讓我們回顧一下“回調(diào)函數(shù)”給我們帶來信任危機的原因: 我們無法信任放入回調(diào)參數(shù)的函數(shù), 因為 它沒有強制要求通過一種確定的(或固定的)形式給我們回調(diào)傳遞有效的信息參數(shù),例如: 異步操作成功的信息, 異步操作失敗的信息,等等。 我們既然都無從得到這些信息, 又怎么能擁有對回調(diào)的控制權(quán)呢? ? 沒錯,我們急需做的的就是得到這些對我們的“回調(diào)”至關(guān)重要的信息(異步操作成功的信息, 異步操作失敗的信息), 并且通過一種規(guī)則讓它們強制地傳遞給我們的回調(diào) ? 讓我們一步步來看看什么是Promise 1.首先Promise是一個可以包含異步操作的對象 new Promise(function() {/* 異步操作? */ }?
2.其次, 這個對象擁有自己的狀態(tài)(state),可以分別用來表示異步操作的“成功”, “失敗”,“正在進行中”。 它們是: Fulfilled: 成功 Rejected:拒絕 Pending: 進行中 ? 3.那怎么控制這三個狀態(tài)的改變呢? 當(dāng)new 一個Promise對象的時候, 我們能接收到兩個方法參數(shù): resolve和reject, 當(dāng)調(diào)用 resolve方法的時候,會把Promise對象的狀態(tài)從Pending變?yōu)镕ulfilled(表示異步操作成功了),當(dāng)調(diào)用 reject方法的時候, 會把Promise對象的狀態(tài)從Pending變?yōu)镽ejected,表示異步操作失敗了, 而如果這兩個函數(shù)沒有調(diào)用,則Promise對象的狀態(tài)一直是Pending(表示異步操作正在進行) ? 我們異步執(zhí)行的函數(shù)可以放在Promise對象里, 然后變成這樣 var promise = new Promise(function(resolve, reject) {// 這里是一堆異步操作的代碼if (/* 異步操作成功 */){resolve(value);} else {reject(error);} });?
4. 最重要的一點, 我們怎么把這個狀態(tài)信息傳遞給我們異步處理后的函數(shù): ? 我們剛剛說了, Promise有Resolved和Rejected兩種狀態(tài), 這兩種狀態(tài)分別對應(yīng)Promise的then方法里的兩個回調(diào)參數(shù) promise.then(function(value) {// 成功 }, function(error) {// 失敗 });?
第一個參數(shù)方法對應(yīng)Resolved, 第二個參數(shù)方法對應(yīng)Rejected 而且Promise成功的時候(調(diào)用resolve), resolve返回的參數(shù)可以被第一個回調(diào)接收到, 如上面的value參數(shù) 而當(dāng)Promise失敗的時候(調(diào)用reject), reject返回的錯誤會被傳遞給第二個回調(diào), 如上面的error ? 【辯解】: 你可能會說:哎呀我們繞了一圈不是又回到了回調(diào)了嗎? Promise好像也不是特別革命性的一個新東西嘛!但是, 我們就圍繞信任問題來說, Promise的確以一種強制的方式, 將回調(diào)的形式固定了下來(兩個方法參數(shù)),并且傳遞了必要的數(shù)據(jù)(異步取得的值或拋出的錯誤)給我們的回調(diào)。 ? 而這樣做,我們已經(jīng)達到了我們的目的: 相對來說,我們使得回調(diào)變得“可控”了, 而不是像單純使用回調(diào)那樣, 因為控制反轉(zhuǎn)而陷入信任危機的噩夢。 打個比方, 讓司機們依據(jù)對自身的道德要求讓不闖紅燈,和通過扣分的機制和法律限制闖紅燈的現(xiàn)象, 無論是性質(zhì)上還是效果上,這兩者之間都是截然不同的。 ?Promise是怎么一個個地解決回調(diào)帶來的問題的
??
?
1.回調(diào)過早調(diào)用 讓我們回到那個回調(diào)的痛點:我們有可能會寫出一個既可能同步執(zhí)行, 又可能異步執(zhí)行的“zalgo”函數(shù)。但Promise可以自動幫我們避免這個問題: ? 如果對一個 Promise 調(diào)用 then(..) 的時候,即使這個 Promise是立即resolve的函數(shù)(即Promise內(nèi)部沒有ajax等異步操作,只有同步操作), 提供給then(..) 的回調(diào)也是會被異步調(diào)用的,這幫助我們省了不少心 ? 2. 回調(diào)調(diào)用次數(shù)過多 Promise 的內(nèi)部機制決定了調(diào)用單個Promise的then方法, 回調(diào)只會被執(zhí)行一次,因為Promise的狀態(tài)變化是單向不可逆的,當(dāng)這個Promise第一次調(diào)用resolve方法, 使得它的狀態(tài)從pending(正在進行)變成fullfilled(已成功)或者rejected(被拒絕)后, 它的狀態(tài)就再也不能變化了 ? 所以你完全不必擔(dān)心Promise.then( function ) 中的function會被調(diào)用多次的情況 ? 3. 回調(diào)中的報錯被吞掉 要說明一點的是Promise中的then方法中的error回調(diào)被調(diào)用的時機有兩種情況: 1. Promise中主動調(diào)用了reject? (有意識地使得Promise的狀態(tài)被拒絕), 這時error回調(diào)能夠接收到reject方法傳來的參數(shù)(reject(error)) 2. 在定義的Promise中, 運行時候報錯(未預(yù)料到的錯誤), 也會使得Promise的狀態(tài)被拒絕,從而使得error回調(diào)能夠接收到捕捉到的錯誤 例如: var p = new Promise( function(resolve,reject){foo.bar(); // foo未定義,所以會出錯!resolve( 42 ); // 永遠不會到達這里 :( } ); p.then(function fulfilled(){// 永遠不會到達這里 :( ??? },function rejected(err){// err將會是一個TypeError異常對象來自foo.bar()這一行 ???? } );?
4. 還有一種情況是回調(diào)根本就沒有被調(diào)用,這是可以用Promise的race方法解決(下文將介紹) // 用于超時一個Promise的工具 function timeoutPromise(delay) {return new Promise( function(resolve,reject){setTimeout( function(){reject( "Timeout!" );}, delay );} ); }// 設(shè)置foo()超時 Promise.race( [foo(), // 試著開始foo()timeoutPromise( 3000 ) // 給它3秒鐘 ] ) .then(function(){// foo(..)及時完成!},function(err){// 或者foo()被拒絕,或者只是沒能按時完成// 查看err來了解是哪種情況} );
?
?Promise的完善的API設(shè)計使得它能夠簡潔優(yōu)雅地處理相對復(fù)雜的場景
鏈式
? 我們上面說了, 純回調(diào)的一大痛點就是“金字塔回調(diào)地獄”, 這種“嵌套風(fēng)格”的代碼丑陋難懂,但Promise就可以把這種“嵌套”風(fēng)格的代碼改裝成我們喜聞樂見的“鏈式”風(fēng)格 因為then函數(shù)是可以鏈式調(diào)用的, 你的代碼可以變成這樣 Promise.then(// 第一個異步操作 ).then(// 第二個異步操作 ).then(// 第三個異步操作 )?
? 而且, 你每一個then里面的異步操作可以返回一個值,傳遞給下一個異步操作 getJSON('/post/1.json').then(function(post) {return getJSON(post.commentURL); }).then(function(comments) {// some code })?
第二個then接收到的comments參數(shù)等于都一個then里面接收到的getJSON(post.commentURL); ? 例如我們上面提到的門
可以使用 Promise.all方法: Promise.all([promise1,promise2 ]) .then(([data1, data2]) =>? getDataAndDoSomething (data1,data2)?
? all方法接收一個Promise數(shù)組,并且返回一個新的“大Promise”, 只有數(shù)組里的全部Promise的狀態(tài)都轉(zhuǎn)為Fulfilled(成功),這個“大Promise”的狀態(tài)才會轉(zhuǎn)為Fulfilled(成功), 這時候, then方法里的成功的回調(diào)接收的參數(shù)也是數(shù)組,分別和數(shù)組里的子Promise一一對應(yīng), 例如promise1對應(yīng)data1,promise2對應(yīng)data2 ? 而如果任意一個數(shù)組里的子Promise失敗了, 這個“大Promise”的狀態(tài)會轉(zhuǎn)為Rejected, 并且將錯誤參數(shù)傳遞給then的第二個回調(diào) ?競態(tài)
可以用Promise.race方法簡單地解決 romise.race方法同樣是將多個Promise實例,包裝成一個新的“大Promise” 例如 var p = Promise.race([p1, p2, p3]);?
上面代碼中,只要p1、p2、p3之中有一個Promise率先改變狀態(tài),p的狀態(tài)就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調(diào)函數(shù)。 ? ? 最后講個小故事 ? 曾經(jīng)我和小伙伴們搞比賽,合并代碼都是通過QQ傳代碼文件然后手動合并,經(jīng)常會為代碼的管理不勝其煩, 遇到諸多問題。一個學(xué)長告訴我可以用git,但我當(dāng)時卻覺得:“用QQ傳代碼合并就很好嘛, 用git的話學(xué)起來又麻煩,合并代碼辛苦一點也很正常的嘛~~~”,直到有一天我真的用上了git這個可愛的版本控制系統(tǒng) —— ? 當(dāng)初勸我用git的學(xué)長的溫暖的身影就浮現(xiàn)出來了....額...就像這樣: ??
如果不對新的東西加以學(xué)習(xí), 你可能不知道舊的東西會給你帶來多少麻煩 如果永遠執(zhí)著于舊的那一套東西, 你可能不知道新的東西能給你帶來多少希望和機遇 ? 所以不要總是說:“用原來的就挺好的呀” ? ? 參考資料:《 你不知道的javascript》—— [美] Kyle Simpson?
轉(zhuǎn)載于:https://www.cnblogs.com/penghuwan/p/7451409.html
總結(jié)
以上是生活随笔為你收集整理的【javascript】异步编年史,从“纯回调”到Promise的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浦发银行腾讯围棋联名信用卡积分怎么计算的
- 下一篇: JDK+Tomcat搭建JSP运行环境-