<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          你全面了解Promise嗎?

          共 20917字,需瀏覽 42分鐘

           ·

          2021-12-15 08:17


          ?點擊上方藍字,關注我們!


          本文字數(shù):8270

          預計閱讀時間:45分鐘




          導讀



          在異步編程中,Promise扮演了舉足輕重的角色,它解決了ajax請求過程中的回調地獄的問題,令代碼更具可讀性。下面的介紹中,筆者會通過一些片段代碼,加上一些其自己的理解帶大家一起重新溫故一下Promise為編程所帶來的便利。

          Promise是抽象異步處理對象以及對其進行各種操作的組件;

          Promise很重要!很重要!很重要!對,要強調三遍,一定要好好掌握。

          例(假如此處你還不是很了解,沒關系,先留個印象):

          var promise = new Promise((resolve,reject) => {    if(true) { resolve(100) };    if(false) { reject('error') };});//使用promise.then(value => {    console.log(value);    //100}).catch(error => {    console.error(error);});


          注意??:本文中的函數(shù)表達式均采用ES6的箭頭函數(shù)表達式的語法,你若還不是很清楚,請自行查閱(參考阮一峰ECMAScript 6入門[1]。


          回調函數(shù)

          剛剛我們說,Promise解決了ajax請求過程中的回調地獄的問題,那么回調(函數(shù))是什么,為什么要用到回調(函數(shù)),我們一起回顧一下。

          回調函數(shù)(callback function),也被稱作高階函數(shù)。

          就是把一個函數(shù)B作為參數(shù)(注意:是作為參數(shù))傳入“另一個函數(shù)A”中,然后這個函數(shù)B在“另一個函數(shù)A”中調用,那么這個函數(shù)B,就叫回調函數(shù)。函數(shù)A執(zhí)行完以后執(zhí)行函數(shù)B,這個過程就叫做回調。

          注意:回調函數(shù)不是立即就執(zhí)行。它是在另一個函數(shù)執(zhí)行完成之后被調用,即在包含的函數(shù)體中指定的地方“回頭調用”。

          也就是:A(主函數(shù))讓 B(參數(shù))做事,B做著做著,信息不夠,不知道怎么做了,就需要A告訴他,這時,A到外面獲取信息,待A執(zhí)行完畢后拿到了所需信息,再回過頭來調用B。

          網(wǎng)上也有一個通俗易懂的例子幫助理解回調函數(shù):

          你到一個商店買東西,剛好你要的東西沒有貨,于是你在店員那里留下了你的電話,過了幾天店里有貨了,店員就打了你的電話,然后你接到電話后就到店里去取了貨。在這個例子里,你的電話號碼就叫回調函數(shù),你把電話留給店員就叫登記回調函數(shù),店里后來有貨了叫做觸發(fā)了回調關聯(lián)的事件,店員給你打電話叫做調用回調函數(shù),你到店里去取貨叫做響應回調事件。

          回調函數(shù)的使用場景:主要是需要將父函數(shù)的執(zhí)行結果通知給回調函數(shù)進行處理時使用。

          //回調函數(shù)舉例1$("#btn").click(() => {  alert("我是回調函數(shù)里的內容,點擊后才出現(xiàn),并沒有立即執(zhí)行");});
          //回調函數(shù)舉例2function runAsyncMain(callback){ callback(); console.log('我是主函數(shù)'); }function runAsyncCallback(){ setTimeout(() => { console.log('我是回調函數(shù)'); }, 2000); //此處模擬延遲加載}runAsyncMain(runAsyncCallback);??//(先輸出)我是主函數(shù)????(2s后輸出)?我是回調函數(shù)

          可見此處主函數(shù)runAsyncMain執(zhí)行的過程中,按順序本應先執(zhí)行回調函數(shù),但輸出結果卻是后輸出的回調函數(shù)內容,這說明,主函數(shù)不用等回調函數(shù)執(zhí)行完再執(zhí)行后續(xù)的語句,可以接著執(zhí)行自己的代碼,等回調函數(shù)準備好,再執(zhí)行回調函數(shù)。所謂的異步加載也不過如此,當然,異步與回調并沒有直接的聯(lián)系,回調只是異步的一種實現(xiàn)方式。


          為什么需要Promise?

          介紹完回調函數(shù),要回到Promise的主場了。程序執(zhí)行過程中,有非常多的應用場景我們不能立即知道應該如何繼續(xù)往下執(zhí)行,例如很重要的ajax請求。通俗來說,由于網(wǎng)速的不同,可能你得到返回值的時間也不同,這時我們就需要等某個結果出來了之后才知道怎么樣繼續(xù)下去,例如下方的回調函數(shù)案例:
          // 需求:當一個ajax結束后,得到的值,需要作為另外一個ajax的參數(shù)被使用(即該參數(shù)得從上一個ajax請求中獲?。?/span>var url = 'XXXXXX';var result;
          var XHR = new XMLHttpRequest();XHR.open('GET', url, true); //第一個ajax請求XHR.send();
          XHR.onreadystatechange = function() { if (XHR.readyState == 4 && XHR.status == 200) { result = XHR.response; console.log(result); // 偽代碼 var url2 = 'XXXXXX' + result.someParams; //通過第一個ajax請求的結果,得到第二個ajax請求所需要的url var XHR2 = new XMLHttpRequest(); XHR2.open('GET', url2, true); //第二個ajax請求 XHR2.send(); XHR2.onreadystatechange = function() { ... //往復上述過程,會得到第三個ajax請求所需要的url值,然后進行第三個ajax請求,在得到第四個…… 超恐怖 } }}
          當上述需求中出現(xiàn)第三個ajax(甚至更多)仍然依賴上一個請求的時候,代碼就會變成一場災難。也就是我們常說的回調地獄。

          這時,我們可能會希望:

          1. 讓代碼變得更具有可讀性和可維護性,減輕一層層套用數(shù)據(jù)和請求的現(xiàn)象;

          2. 將請求和數(shù)據(jù)處理明確的區(qū)分開。

          這時Promise就要閃亮登場了,Promise中強大的then方法,可以解決剛剛出現(xiàn)的恐怖的回調地獄問題,并且讓代碼更優(yōu)雅。

          別急,我們先從文檔中最基礎的 API 入手。


          Promise的API

          1、constructor (構造函數(shù)屬性)

          Promise本身也是一個構造函數(shù),需要通過這個構造函數(shù)創(chuàng)建一個新的Promise對象作為接口,使用new來調用Promise的構造器來進行實例化,所以這個實例化出來的新對象:具有constructor屬性,并且指針指向他的構造函數(shù)Promise。

          var promise = new Promise((resolve, reject) => {    // 此處代碼會立即執(zhí)行    // 當調用棧內容處理結束后,再通過promise.then()方法調用resolve 或 reject返回的數(shù)據(jù)});


          2、Instance Method (實例方法)

          promise.then()

          Promise對象中的promise.then(resolve,reject)實例方法,可以接收構造函數(shù)中處理的狀態(tài)變化,通過此方法,設置了其值在resolve(成功)/reject(失敗)時調用的回調函數(shù),并分別對應執(zhí)行。

          promise.then(onFulfilled, onRejected)

          then方法有2個參數(shù)(都是可選參數(shù),此參數(shù)是個回調函數(shù)):

          • resolve成功時onFulfilled會被調用;

          • reject失敗時onRejected會被調用。

          promise.then成功和失敗時都可以使用,并且then方法的執(zhí)行結果也會返回一個Promise對象


          promise.catch()

          另外在只想對異常進行處理時可以采用promise.then(undefined, onRejected)這種方式,只指定reject時的回調函數(shù)即可。不過這種情況下 promise.catch(onRejected)應該是個更好的選擇。

          promise.catch(onRejected)


          注意:在IE8及以下版本,使用promise.catch()的代碼,會出現(xiàn)identifier not found的語法錯誤。(因為catch與ECMAScript的保留字[2](Reserved Word)有關,在ECMAScript 3中保留字是不能作為對象的屬性名使用的。)

          解決辦法:不單純的使用catch,而是使用then來避免這個問題。

          //then和catch方法 舉例function asyncFunction(value) {    var p = new Promise((resolve, reject) => {        if(typeof(value) == 'number'){            resolve("我是數(shù)字");        }else {            reject("我不是數(shù)字");        }    });    return p;}
          // 寫法1:同時使用then和catch方法asyncFunction(123).then(value => { console.log(value); }).catch(error => { console.log(error);});//執(zhí)行結果:我是數(shù)字//解說:執(zhí)行過程 // 1、在asyncFunction這個方法中創(chuàng)建一個Promise構造函數(shù)p,只要執(zhí)行asyncFunction()就會返回一個Promise對象;// 2、asyncFunction方法表示:如果傳入的參數(shù)value值是數(shù)字,就輸出"我是數(shù)字",如果不是數(shù)字,就輸出"我不是數(shù)字"// 3、寫法1中,為promise對象用設置 .then 方法調用返回值時的回調函數(shù),.catch 方法來設置發(fā)生錯誤時的回調函數(shù)// 4、promise對象會先判斷resolve,并執(zhí)行then方法,如果resolve執(zhí)行通過,則不會執(zhí)行catch方法,若上面代碼在執(zhí)行中產(chǎn)生異常,在 catch 中設置的回調函數(shù)就會被執(zhí)行,并輸出error。
          // 寫法2:只使用 then方法,不使用catch 方法asyncFunction('abc').then((value) => { console.log(value); },(error) => { console.log(error);});//執(zhí)行結果:我不是數(shù)字


          3、Static Method (靜態(tài)方法)

          Promise這樣的全局對象還擁有一些靜態(tài)方法,后文中會有詳細解釋。

          Promise.resolve()

          Promise.resolve()方法返回一個已給定值解析后的新的Promise對象,從而能繼續(xù)使用then的鏈式方法調用。

          Promise.reject()

          Promise.reject()方法和Promise.resolve()方法一樣。

          Promise.all()

          Promise.all()方法的作用是將多個Promise對象實例包裝,生成并返回一個新的Promise實例。

          Promise.race()

          Promise.race()與Promise.all()相反。


          Promise的狀態(tài) (Fulfilled、Rejected、Pending)

          Promise的精髓是“狀態(tài)”,用維護狀態(tài)、傳遞狀態(tài)的方式來使得回調函數(shù)能夠及時調用。

          new Promise實例化的promise對象有以下三個狀態(tài):

          • "unresolved" -?Pending| 既不是resolve也不是reject的狀態(tài)。等待中,或者進行中,表示Promise剛創(chuàng)建,還沒有得到結果時的狀態(tài);

          • "has-resolution" - Fulfilled| resolve(成功)時。此時會調用onFulfilled;

          • "has-rejection" - Rejected| reject(失敗)時。此時會調用onRejected

          關于下面這三種狀態(tài)的讀法,其中左側為在ES6 Promises規(guī)范中定義的術語,而右側則是在Promises/A+中描述狀態(tài)的術語。

          上圖的意思是promise對象的狀態(tài),從Pending轉換為FulfilledRejected之后, 這個promise對象的狀態(tài)就不會再發(fā)生任何變化,會一直保持這個結果。

          當promise的對象狀態(tài)發(fā)生變化時,用.then來定義只會被調用一次的函數(shù)。


          Promise的使用

          1、創(chuàng)建Promise對象

          前面很多次強調,Promise本身就是一個構造函數(shù),所以可以通過new創(chuàng)建新的Promise對象:

          var p = new Promise((resolve, reject) => {    //做一些異步操作    setTimeout(() => {        console.log('執(zhí)行完成');        resolve('我的數(shù)據(jù)');    }, 0);    console.log("我先執(zhí)行")});//先輸出:我先執(zhí)行//1秒之后輸出:執(zhí)行完成

          我們執(zhí)行了一個異步操作,也就是setTimeout,1秒后,輸出“執(zhí)行完成”,并且調用resolve方法。但是只是new了一個Promise對象,并沒有調用它,我們傳進去的函數(shù)就已經(jīng)執(zhí)行了。為了避免這個現(xiàn)象產(chǎn)生,所以我們用Promise的時候一般是包在一個函數(shù)中,需要的時候去運行這個函數(shù)。

          如果你對執(zhí)行的先后順序還不理解,推薦閱讀文章事件的循環(huán)機制(Event loop)[3]前文中我們也曾多次提到異步加載,所以此概念應熟記于心。

          異步任務:指不進入主線程、而進入"任務隊列"(task queue)的任務,只有等主線程任務執(zhí)行完畢,“任務隊列”開始通知主線程,請求執(zhí)行任務,該任務才會進入主線程執(zhí)行。

          異步加載例如:你燒壺水要沖咖啡,可是水要10分鐘才能燒開,此時,你轉身去寫了個小程序,等10分鐘后水好了,才回來繼續(xù)沖咖啡的活動,中間你去做了很多別的有意義的事情,也并沒有耽誤沖咖啡這項任務,這就是異步。

          也可以理解為可以改變程序正常執(zhí)行順序的操作就可以看成是異步操作。例如setTimeout和setInterval函數(shù)。

          同步任務:指在主線程上排隊執(zhí)行的任務,只有前一個任務執(zhí)行完畢,才能執(zhí)行后一個任務;

          同步加載例如:你燒壺水要沖咖啡,可是水要10分鐘才能燒開,此時,你就等啊等啊等,等了10分鐘水好了,才繼續(xù)沖咖啡的活動,中間的過程就是等待,啥都不干,這就是同步。

          推薦參考文章:徹底理解同步、異步和事件循環(huán)(Event Loop)[4]

          你會發(fā)現(xiàn),異步加載的舉例和上文中強調的回調函數(shù)例子很像,但剛剛也強調了,異步與回調并沒有直接的聯(lián)系,回調只是異步的一種實現(xiàn)方式(再次重復,加深理解)。

          你會不會以為異步像是多線程操作那樣并列執(zhí)行程序?我想說,千萬不要這樣想!js就是單線程,沒有多線程一說,所以不存在并行,即便是異步,也是單線程,只不過是放在了異步隊列里,對,是隊列,倘若你不是很理解,那么請前去了解一下事件循環(huán)機制中的宏任務和微任務(promise就是微任務,settimeout是宏任務,非常不錯的一篇文章)的區(qū)別,它們所在的隊列是不同的,看過之后,相信你會對promise有更深刻的了解。


          2、封裝Promise對象

          function asyncFunction(num) {    var p = new Promise((resolve, reject) => {  //創(chuàng)建一個Promise的新對象p        if (typeof num == 'number') {            resolve();        } else {            reject();        }    });    p.then(function() {  //這個(第一個)function是resolve對應的參數(shù)        console.log('這個是數(shù)字');    }, function() {    //這個(第二個)function是reject對應的參數(shù)        console.log('我不是數(shù)字');    })    return p;  //此處返回對象p}
          //執(zhí)行這個函數(shù)我們得到了一個Promise構造出來的對象p,所以p.__proto__ === Promise.prototype,即p的指針指向了構造函數(shù)Promise,因此asyncFunction()能夠使用Promise的屬性和方法
          //此種寫法可以多次調用asyncFunction這個方法asyncFunction('hahha'); //我不是數(shù)字asyncFunction(1234);??//這個是數(shù)字

          我們剛剛講到,then方法的執(zhí)行結果也會返回一個Promise對象,得到一個結果。因此我們可以進行then的鏈式執(zhí)行,接收上一個then返回回來的數(shù)據(jù)并繼續(xù)執(zhí)行,這也是解決回調地獄的主要方式。


          3、Promise的鏈式操作和數(shù)據(jù)傳遞

          下面我們就來看看.then和.catch這兩個方法返回的到底是不是新的promise對象。

          var aPromise = new Promise(resolve => {    resolve(100);});// aPromise為原promise對象// 下面分開進行.then和.catch操作var thenPromise = aPromise.then(value => {    console.log(value);});var catchPromise = thenPromise.catch(error => {    console.error(error);});console.log(aPromise !== thenPromise); // => trueconsole.log(aPromise !== catchPromise); // => trueconsole.log(thenPromise !== catchPromise);// => true

          ===是嚴格相等比較運算符,我們可以看出這三個對象都是互不相同的,這也就證明了thencatch都返回了和調用不同的promise對象。我們通過下面這個例子進一步來理解:

          // 1: 對同一個promise對象同時調用 `then` 方法var aPromise = new Promise(resolve => {    resolve(100);});aPromise.then(value => {    return value * 2;});aPromise.then(value => {    return value * 2;});aPromise.then(value => {    console.log("1: " + value); // 1: 100})
          // vs
          // 2: 對 `then` 進行 promise 鏈式 方式進行調用var bPromise = new Promise(resolve => { resolve(100);});bPromise.then(value => { return value * 2;}).then(value => { return value * 2;}).then(value => { console.log("2: " + value); // 2: 400});

          寫法1 中并沒有使用promise的方法鏈方式,這在Promise中應該是極力避免的寫法。這種寫法中的then調用幾乎是同時開始執(zhí)行的,而且傳給每個then方法的value值都是100。

          寫法2 則采用了方法鏈的方式將多個then方法調用串連在了一起,各函數(shù)也嚴格按照 resolve → then → then → then 的順序執(zhí)行,并且傳給每個then方法的value的值都是前一個promise對象通過return返回的值,實現(xiàn)了Promise的數(shù)據(jù)傳遞。

          強調:promise的鏈式操作實現(xiàn)了數(shù)據(jù)的傳遞,promise非鏈式操作的方法無法實現(xiàn)數(shù)據(jù)傳遞。


          4、通過Promise封裝ajax解決回調地獄問題

          剛剛在開篇(【為什么需要Promise】 這一節(jié)),通過一個ajax的例子,引出了回調地獄的概念,強調了通過回調函數(shù)方式解決多級請求都依賴于上一級數(shù)據(jù)時所引發(fā)的問題。下面我們通過剛剛學習過的Promise內容(特別是.then的鏈式數(shù)據(jù)傳遞)對上面的ajax數(shù)據(jù)依賴的案例進行重寫:

          var url = 'XXXXX';
          // 封裝一個get請求的方法function getJSON(url) { return new Promise((resolve, reject) => { var XHR = new XMLHttpRequest(); XHR.open('GET', url, true); XHR.send();
          XHR.onreadystatechange = function() { if (XHR.readyState == 4) { if (XHR.status == 200) { try { var response = JSON.parse(XHR.responseText); resolve(response); } catch (e) { reject(e); } } else { reject(new Error(XHR.statusText)); } } } })}
          getJSON(url) .then(resp => { console.log(response); return url2 = 'http:xxx.yyy.com/zzz?ddd=' + resp; }) .then(resp => { console.log(response); return url3 = 'http:xxx.yyy.com/zzz?ddd=' + resp; });


          new Promise寫法的快捷方式

          1、Promise.resolve

          new Promise(resolve => {    resolve(100);});// 等價于Promise.resolve(100);  //Promise.resolve(100); 可以認為是上述代碼的語法糖。
          // 使用方法Promise.resolve(100).then(value => { console.log(value);});


          另:Promise.resolve方法另一個作用就是將thenable對象轉換為promise對象。

          ES6 Promises提到了Thenable這個概念,簡單來說它就是一個非常類似promise的東西。就像我們有時稱具有.length方法的非數(shù)組對象為類數(shù)組(Array like)一樣,thenable指的是一個具有.then方法的對象。

          因為:類庫沒有提供Promise的實現(xiàn),用戶通過Promise.resolve(thenable)來自己實現(xiàn)了Promise,并且,作為Promise使用的時候,需要和Promise.resolve(thenable)一起配合使用,將thenable對象轉換promise對象:

          var promise = Promise.resolve($.ajax('/json/comment.json'));// => promise對象promise.then(function(value){   console.log(value);});


          在此不再對Thenable進行過多贅述,可自行了解。


          2、Promise.reject

          new Promise((resolve,reject) => {    reject(new Error("出錯了"));});// 等價于 Promise.reject(new Error("出錯了"));  // Promise.reject(new Error("出錯了")) 就是上述代碼的語法糖。
          // 使用方法Promise.reject(new Error("BOOM!")).catch(error => { console.error(error);});


          Promise.all()

          Promise.all接收一個promise對象的數(shù)組作為參數(shù),當這個數(shù)組里的所有promise對象全部變?yōu)閞esolve或reject狀態(tài)的時候,它才會去調用.then方法。

          也就是說:Promise的all方法提供了異步操作的能力,并且在所有異步操作執(zhí)行完后才執(zhí)行回調。

          // `delay`毫秒后執(zhí)行resolvefunction timerPromisefy(delay) {    return new Promise(resolve => {        setTimeout(() => {            resolve(delay);        }, delay);    });}var startDate = Date.now();// 所有promise變?yōu)閞esolve后程序退出Promise.all([    timerPromisefy(1),    timerPromisefy(32),    timerPromisefy(64),    timerPromisefy(128)]).then(values => {    console.log(Date.now() - startDate + 'ms');    // 約128ms    console.log(values);    // [1,32,64,128]});

          這說明timerPromisefy會每隔1、32、64、128ms都會有一個promise發(fā)生resolve行為,返回一個promise對象,狀態(tài)為FulFilled,其狀態(tài)值為傳給timerPromisefy的參數(shù),并且all會把所有異步操作的結果放進一個數(shù)組中傳給then。

          從上述結果可以看出,傳遞給Promise.all的promise并不是一個個的順序執(zhí)行的,而是同時開始、并行執(zhí)行的。


          Promise.race()

          all方法的效果實際上是「誰跑的慢,以誰為準執(zhí)行回調」,那么相對的就有另一個方法「誰跑的快,以誰為準執(zhí)行回調」,這就是race方法,這個詞本來就是賽跑的意思。race的用法與all一樣,接收一個promise對象數(shù)組為參數(shù)。

          Promise.all在接收到的所有的對象promise都變?yōu)镕ulFilled或者Rejected狀態(tài)之后才會繼續(xù)進行后面的處理,與之相對的是Promise.race只要有一個promise對象進入FulFilled或者Rejected狀態(tài)的話,就會繼續(xù)進行后面的處理。

          // `delay`毫秒后執(zhí)行resolvefunction timerPromisefy(delay) {    return new Promise(resolve => {        setTimeout(() => {            resolve(delay);        }, delay);    });}// 任何一個promise變?yōu)閞esolve或reject 的話程序就停止運行Promise.race([    timerPromisefy(1),    timerPromisefy(32),    timerPromisefy(64),    timerPromisefy(128)]).then(function (value) {    console.log(value);    // => 1});

          面的代碼創(chuàng)建了4個promise對象,這些promise對象會分別在1ms、32ms、64ms和128ms后變?yōu)榇_定狀態(tài),即FulFilled,并且在第一個變?yōu)榇_定狀態(tài)的1ms后,.then注冊的回調函數(shù)就會被調用,這時候確定狀態(tài)的promise對象會調用resolve(1)因此傳遞給value的值也是1,控制臺上會打印出1來。


          promise的基本使用原理以及它在實際應用中為我們解決的問題,在上述過程中已經(jīng)介紹完了,你是否理解了呢?學習是一個反復閱讀,反復加深印象的過程,加油牢牢掌握這一知識點,在vue、react等框架的使用中,也會頻繁用到有關promise的知識,下面一起來檢測一下對promise的認知結果吧。


          小練習

          下面內容的輸出結果應該是啥?

          function test1() {    console.log("test1");}function test2() {    console.log("test2");}function onRejected(error) {    console.log("捕獲錯誤: test1 or test2", error);}function test3() {    console.log("end");}
          var promise = Promise.resolve();promise .then(test1) .then(test2) .catch(onRejected) .then(test3);


          溫馨提示:這里沒有為then方法指定第二個參數(shù)(onRejected)。


          補充1:對比 callback → Promise → async/await

          javascript的異步發(fā)展歷程,從callback,到Promise對象、Generator函數(shù),不停地優(yōu)化程序上的編寫方式,但又讓人覺得不是很徹底,隨即又有了之后的async/await的異步編程方式,讓異步編程變得更像同步代碼,增強了代碼的可讀性,甚至很多人評價async/await是異步操作的終極解決方案,接下來簡單介紹一下這三種方式各自的優(yōu)缺點:

          1.callback(回調):本文開篇也提及了回調函數(shù)雖然好理解,但只對于簡單的異步程序,callback是可以勝任的,但是在ajax需要被多次調用時使用起來還是會產(chǎn)生很多問題:

          • 高耦合,讓程序變得難以維護;

          • 并且錯誤捕捉要通過人工的設置判斷來進行。

          2.Promise:ES6提供的構造函數(shù)Promise的實現(xiàn)是要基于callback的,解決了異步執(zhí)行的問題:

          • 通過Promise.then()鏈式調用的方法,解決了回調函數(shù)層層嵌套(回調地獄)的問題,讓代碼和操作都變得更加簡潔;

          • 可以統(tǒng)一通過Promise.catch()方法對異常進行捕獲,無需再像callback那樣,為每個異步操作添加異常處理;

          • Promise.all()方法可以對異步操作進行并行處理,同時執(zhí)行多個操作。

          但Promise也存在缺點:

          • 當處于未完成狀態(tài)時,無法確定目前處于哪一階段;

          • 如果不設置回調函數(shù),Promise內部的錯誤不會反映到外部;

          • Promise一旦新建它就會立即執(zhí)行,無法中途取消。

          ES6中,還有一個generator函數(shù),以前一個函數(shù)中的代碼要么被調用,要么不被調用,不存在能暫停的情況,generator函數(shù)讓代碼可以中途暫停、異步執(zhí)行,它與Promise的結合使用,類似于async/await(見下文)效果的代碼。

          整個Generator函數(shù)就是一個封裝的異步任務的容器。它的語法是在函數(shù)名前加個*號,在異步操作需要暫停的地方,都用yield語句注明,但僅有yield,函數(shù)是不會執(zhí)行的,他需要調用next方法,指針都會向下移一個狀態(tài),直到遇到下一個yield表達式(或return語句)為止。

          3.async/await:ES7中新增的異步編程方法,async/await的實現(xiàn)是基于 Promise的,簡單而言就是async function就是返回Promise的function,是generator的語法糖,其實async函數(shù)就是將Generator函數(shù)的星號(*)替換成 async,將yield替換成await。很多人認為async/await是異步操作的終極解決方案:

          • 改進JS中異步操作串行執(zhí)行的代碼組織方式,減少callback的嵌套;

          • 語法簡潔,更像是同步代碼,也更符合普通的閱讀習慣;

          • Promise中不能自定義使用try/catch進行錯誤捕獲,但是在Async/await中可以像處理同步代碼處理錯誤。

          綜上異步編程的演變過程可見,它語法目標,其實就是怎樣讓它更像同步編程。

          如果你想要更詳細的了解Generator和async/await異步操作方式,請查閱MDN中的相關文檔。同時推薦一篇通俗易懂的文章:https://www.lazycoffee.com/articles/view?id=58ab09eea072b332753d9774


          補充2:Promise 的實現(xiàn)原理

          一起回顧一下, 前面我們介紹了promise的來由、解決的問題、常用和重點的方法、以及promise的使用方法和應用場景,你是不是也很好奇,沒有promise的話,我們要如何模擬出promise呢?也就是,promise是如何實現(xiàn)的?

          回顧一下Promise的使用過程:

          1.首先要知道,Promise對象有三個狀態(tài):Pending(進行中)、Fulfilled(已成功)Rejected(已失敗),所以需要Promise設置三個狀態(tài)值;

          2.Promise存在resolve和reject兩個回調函數(shù)作為自身參數(shù):new Promise((resolve, reject){});,通過判斷步驟1中的三個狀態(tài)值,來確定輸出哪個方法,例如:

          • promise處于Fulfilled狀態(tài)時,就輸出resolve對應的方法;

          • Rejected狀態(tài)時就輸出reject方法。

          3.Promise的then方法要接收兩個參數(shù):promise.then(onFulfilled, onRejected),onFulfilled和onRejected也必須是兩個函數(shù):

          • 當Promise的狀態(tài)為成功時,調用onFulfilled這個方法,其中onFulfilled方法中的參數(shù)是步驟2中promise成功狀態(tài) resolve執(zhí)行時傳入的值;

          • 當Promise的狀態(tài)為失敗時,調用onRejected這個方法,其中onRejected方法中的參數(shù)是步驟2中promise失敗狀態(tài)reject執(zhí)行時傳入的值。

          4.如果then被同一個Promise多次調用,所有onFulfilledonRejected需按照其注冊順序依次回調;

          5.……

          6.當然,Promise還有.catch、.all、.race等很多方法,以及基本邏輯和規(guī)則確定后,還需要加上錯誤捕獲、值傳遞等機制,例如判斷步驟2中的回調函數(shù)是否為function、步驟4鏈式操作中,then返回的是否為一個新的Promise對象等等;

          具體的Promise實現(xiàn)原理代碼如下,若有興趣的童鞋可參考閱讀,幫助進一步加深理解:

          // 判斷變量否為function  const isFunction = variable => typeof variable === 'function'  // 定義Promise的三種狀態(tài)常量  const PENDING = 'PENDING'  const FULFILLED = 'FULFILLED'  const REJECTED = 'REJECTED'
          class MyPromise { constructor (handle) { if (!isFunction(handle)) { throw new Error('MyPromise must accept a function as a parameter') } // 添加狀態(tài) this._status = PENDING // 添加狀態(tài) this._value = undefined // 添加成功回調函數(shù)隊列 this._fulfilledQueues = [] // 添加失敗回調函數(shù)隊列 this._rejectedQueues = [] // 執(zhí)行handle try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } } // 添加resovle時執(zhí)行的函數(shù) _resolve (val) { const run = () => { if (this._status !== PENDING) return // 依次執(zhí)行成功隊列中的函數(shù),并清空隊列 const runFulfilled = (value) => { let cb; while (cb = this._fulfilledQueues.shift()) { cb(value) } } // 依次執(zhí)行失敗隊列中的函數(shù),并清空隊列 const runRejected = (error) => { let cb; while (cb = this._rejectedQueues.shift()) { cb(error) } } /* 如果resolve的參數(shù)為Promise對象,則必須等待該Promise對象狀態(tài)改變后, 當前Promsie的狀態(tài)才會改變,且狀態(tài)取決于參數(shù)Promsie對象的狀態(tài) */ if (val instanceof MyPromise) { val.then(value => { this._value = value this._status = FULFILLED runFulfilled(value) }, err => { this._value = err this._status = REJECTED runRejected(err) }) } else { this._value = val this._status = FULFILLED runFulfilled(val) } } // 為了支持同步的Promise,這里采用異步調用 setTimeout(run, 0) } // 添加reject時執(zhí)行的函數(shù) _reject (err) { if (this._status !== PENDING) return // 依次執(zhí)行失敗隊列中的函數(shù),并清空隊列 const run = () => { this._status = REJECTED this._value = err let cb; while (cb = this._rejectedQueues.shift()) { cb(err) } } // 為了支持同步的Promise,這里采用異步調用 setTimeout(run, 0) } // 添加then方法 then (onFulfilled, onRejected) { const { _value, _status } = this // 返回一個新的Promise對象 return new MyPromise((onFulfilledNext, onRejectedNext) => { // 封裝一個成功時執(zhí)行的函數(shù) let fulfilled = value => { try { if (!isFunction(onFulfilled)) { onFulfilledNext(value) } else { let res = onFulfilled(value); if (res instanceof MyPromise) { // 如果當前回調函數(shù)返回MyPromise對象,必須等待其狀態(tài)改變后在執(zhí)行下一個回調 res.then(onFulfilledNext, onRejectedNext) } else { //否則會將返回結果直接作為參數(shù),傳入下一個then的回調函數(shù),并立即執(zhí)行下一個then的回調函數(shù) onFulfilledNext(res) } } } catch (err) { // 如果函數(shù)執(zhí)行出錯,新的Promise對象的狀態(tài)為失敗 onRejectedNext(err) } } // 封裝一個失敗時執(zhí)行的函數(shù) let rejected = error => { try { if (!isFunction(onRejected)) { onRejectedNext(error) } else { let res = onRejected(error); if (res instanceof MyPromise) { // 如果當前回調函數(shù)返回MyPromise對象,必須等待其狀態(tài)改變后在執(zhí)行下一個回調 res.then(onFulfilledNext, onRejectedNext) } else { //否則會將返回結果直接作為參數(shù),傳入下一個then的回調函數(shù),并立即執(zhí)行下一個then的回調函數(shù) onFulfilledNext(res) } } } catch (err) { // 如果函數(shù)執(zhí)行出錯,新的Promise對象的狀態(tài)為失敗 onRejectedNext(err) } } switch (_status) { // 當狀態(tài)為pending時,將then方法回調函數(shù)加入執(zhí)行隊列等待執(zhí)行 case PENDING: this._fulfilledQueues.push(fulfilled) this._rejectedQueues.push(rejected) break // 當狀態(tài)已經(jīng)改變時,立即執(zhí)行對應的回調函數(shù) case FULFILLED: fulfilled(_value) break case REJECTED: rejected(_value) break } }) } // 添加catch方法 catch (onRejected) { return this.then(undefined, onRejected) } // 添加靜態(tài)resolve方法 static resolve (value) { // 如果參數(shù)是MyPromise實例,直接返回這個實例 if (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value)) } // 添加靜態(tài)reject方法 static reject (value) { return new MyPromise((resolve ,reject) => reject(value)) } // 添加靜態(tài)all方法 static all (list) { return new MyPromise((resolve, reject) => { /** * 返回值的集合 */ let values = [] let count = 0 for (let [i, p] of list.entries()) { // 數(shù)組參數(shù)如果不是MyPromise實例,先調用MyPromise.resolve this.resolve(p).then(res => { values[i] = res count++ // 所有狀態(tài)都變成fulfilled時返回的MyPromise狀態(tài)就變成fulfilled if (count === list.length) resolve(values) }, err => { // 有一個被rejected時返回的MyPromise狀態(tài)就變成rejected reject(err) }) } }) } // 添加靜態(tài)race方法 static race (list) { return new MyPromise((resolve, reject) => { for (let p of list) { // 只要有一個實例率先改變狀態(tài),新的MyPromise的狀態(tài)就跟著改變 this.resolve(p).then(res => { resolve(res) }, err => { reject(err) }) } }) } finally (cb) { return this.then( value => MyPromise.resolve(cb()).then(() => value), reason => MyPromise.resolve(cb()).then(() => { throw reason }) ); } }
          // 源碼鏈接:https://juejin.im/post/5b83cb5ae51d4538cc3ec354



          參考:

          [1].阮一峰ECMAScript 6入門(http://es6.ruanyifeng.com/#docs/function#箭頭函數(shù))

          [2].保留字(http://mothereff.in/js-properties#catch)

          [3].事件的循環(huán)機制(Event loop)(https://www.jianshu.com/p/12b9f73c5a4f)~~

          [4].徹底理解同步、異步和事件循環(huán)(Event Loop)(https://segmentfault.com/a/1190000004322358?utm_source=tag-newest) (https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop)

          [5].JavaScript Promise迷你書(http://liubin.org/promises-book/)

          [6].透徹掌握Promise的使用(https://www.jianshu.com/p/fe5f173276bd)

          瀏覽 84
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九一精品在线观看 | 中文字幕天天干 | 久久骚逼视频 | 精品无码少妇一区二区三区 | www.好吊操 |