国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

一起來學(xué)Promise

liaoyg8023 / 396人閱讀

摘要:參數(shù)如前面所提到的,方法只是方法的一個語法糖,原因就在于方法的參數(shù)為實際上是兩個回調(diào)函數(shù),分別用于處理調(diào)用它的對象的和狀態(tài),而方法就等價于狀態(tài)處理函數(shù)。對象狀態(tài)傳遞和改變的方法利用回調(diào)的返回值,可以控制某個操作后方法返回的對象及其狀態(tài)。

注意,本文主要針對ES6標(biāo)準(zhǔn)實現(xiàn)的Promise語法進(jìn)行闡述,實例代碼也都使用ES6語法,快速入門ES6請參見ECMAScript 6 掃盲。

一分鐘快速入門

被回調(diào)地獄整怕了?快試Promise吧!。Promise的核心思想其實很簡單,就是將異步操作結(jié)果處理交給Promise對象的方法注冊,然后等到異步操作完了再去取用這些處理操作。至于取用哪個處理操作,就得看Promise對象狀態(tài)了。Promise對象一共有三種狀態(tài):Pending(初始狀態(tài))、Fulfilled(異步操作成功)、Rejected(異步操作失敗)。而三者間的轉(zhuǎn)換只有兩種情況:Pending—>Fulfilled、Pending—>Rejected;詳見下圖:

了解了狀態(tài)及其轉(zhuǎn)換后,我們就可以來使用Promise對象了:

let promise = new Promise((resolve, reject)=> {
    // 異步操作
    // 異步操作成功時調(diào)用
    resolve(value)
    // 異步操作失敗時調(diào)用
    reject(error)
    });

上述代碼中傳給Promise構(gòu)造函數(shù)的兩個函數(shù)resolve, reject,分別用于觸發(fā)Promise對象的Fullfilled和Rejected狀態(tài)。當(dāng)處于Fullfilled狀態(tài)時Promise會調(diào)用then方法,而處于Rejected狀態(tài)時則會調(diào)用catch方法,這兩個方法都會返回Promise對象,所以我們可以采用鏈?zhǔn)綄懛ǎ?/p>

promise.then((value)=> {...})
    .catch((error)=> {...});

上面的方法鏈中,then方法里注冊了Fullfilled狀態(tài)的處理函數(shù)、catch方法則注冊了Rejected狀態(tài)的處理函數(shù)。這種簡單明了的寫法把異步操作的結(jié)果處理函數(shù)分離了出來,如果這些處理本身又是異步操作,那我們自然也就把層層異步回調(diào)也從回調(diào)地獄中剝離了,代碼瞬間清爽有木有!

深入Promise調(diào)用鏈

前面我們只是將一層處理操作分離到then方法中(其中catch方法只是then方法的一個語法糖,后面會再作講解);但在實際應(yīng)用中多個異步操作往往會以串行或并行的方式連續(xù)出現(xiàn),比如下面這個預(yù)定房間的流程:

其中數(shù)據(jù)校驗、向API發(fā)送請求、往數(shù)據(jù)庫插入數(shù)據(jù)都是異步操作,一種用回調(diào)的寫法大概長這樣:

validate(data, (err)=> {
    if (err) return errorHandler(err);
    request(apiUrl, (err, apiResponse)=> {
            if (err) return errorHandler(err);
            if (apiResponse.isSuccessful) insertToDB(data, (err)=> {
                    if (err) return errorHandler(err);
                    successHandler();
                });
            else errorHandler(new Error("API error"));
        });
    });

根據(jù)前面我們了解的Promise用法,我們已經(jīng)能將validate這個異步操作寫成Promise形式了:

let promiseValidate = new Promise((resolve, reject)=> {
    validate(data, (err)=> {
        if (err) return reject(err);
        resolve();
        });
    });

promiseValidate(data)
    .then(()=> {
        request(apiUrl, (err, apiResponse)=> {
                if (err) return errorHandler(err);
                if (apiResponse.isSuccessful) insertToDB(data, (err)=> {
                        if (err) return errorHandler(err);
                        successHandler();
                    });
                else errorHandler(new Error("API error"));
            });
        })
    .catch((err)=> errorHandler(err));

但要改就改到底,上面這種Promise和回調(diào)寫法混合得就不倫不類,除了仍存在回調(diào)嵌套的問題,多次出現(xiàn)的錯誤判斷和處理也有點違反DRY。所以接下來我們會深入研究下Promise調(diào)用鏈的行為,重點探討then方法里注冊的回調(diào)對調(diào)用鏈上數(shù)據(jù)傳遞和Promise對象狀態(tài)變化的影響,以及如何在調(diào)用鏈上對錯誤進(jìn)行統(tǒng)一的處理。

Promise.resolve和Promise.reject

我們先來看下一種“快速”生成Promise對象的方法:直接調(diào)用Promise.resolve(value)Promise.reject(err)。這種方法和new一個Promise對象的區(qū)別在于,Promise對象在生成的時候狀態(tài)就已經(jīng)確定,要么是Fullfilled(使用Promise.resolve())、要么是Rejected(使用Promise.reject()),不會和new實例化一樣等要異步操作完了再發(fā)生變化。

此外,如果傳給Promise.resolve方法的是一個具有then方法的對象(即所謂的Thenable對象),比如jQuery的$.ajax(),那么返回的Promise對象,后續(xù)調(diào)用的then就是原對象then方法的同一形式(參見下面的代碼)。簡單來講,就是Promise.resolve會將Thenable對象轉(zhuǎn)為ES6的Promise對象,這一特性常被用來將Promise的不同實現(xiàn)轉(zhuǎn)換為ES6實現(xiàn)。

$.ajax("https://httpbin.org/ip").then((value)=> {
    /* 輸出223.65.191.59 */
    console.log(value.origin)
    });

Promise.resolve($.ajax("https://httpbin.org/ip"))
    .then((value)=> {
        /* 輸出223.65.191.59 */
        console.log(value.origin)
        });
詳解Promise.prototype.then

有了前面知識的鋪墊,我們終于可以來詳細(xì)講一下Promise對象的then方法了。

參數(shù)

如前面所提到的,catch方法只是then方法的一個語法糖,
原因就在于then方法的參數(shù)為實際上是“兩個”回調(diào)函數(shù),分別用于處理調(diào)用它的Promise對象的Fullfilled和Rejected狀態(tài),而catch方法就等價于then(undefined, Rejected狀態(tài)處理函數(shù))

關(guān)于這兩個回調(diào)函數(shù),首先要注意它們是異步調(diào)用的:

var v = 1;
/* 輸出result: 2 */
Promise.resolve().then(()=> {console.log("result: " + v)});
/* 輸出result: 2 */
Promise.reject().then(undefined, ()=> {console.log("result: " + v)});
v++;

而兩個回調(diào)函數(shù)的參數(shù),則是通過調(diào)用then方法的Promise對象指定的:

new Promise()產(chǎn)生的Promise對象,會分別用內(nèi)部resolve()reject()函數(shù)的參數(shù)

Promise.resolve()Promise.reject()產(chǎn)生的Promise對象,則分別用Promise.resolve()Promise.reject()的參數(shù)

而兩個回調(diào)函數(shù)的返回值,會用Promise.resolve(第一個回調(diào)返回值)Promise.reject(第二個回調(diào)返回值)的形式作包裝,用來“替換”then方法返回的Promise對象。結(jié)合上面提到的then回調(diào)函數(shù)參數(shù)指定方式,回調(diào)返回值會這樣影響下一個then的回調(diào)函數(shù):

返回的是普通數(shù)據(jù),會傳給下一級調(diào)用的then方法作為回調(diào)函數(shù)的參數(shù)

返回的是Promise對象或Thenable對象,會被拿來“替換”then方法返回的Promise對象,具體then的回調(diào)函數(shù)怎么調(diào)用和傳參就得看其內(nèi)部實現(xiàn)了

返回值

一個新的Promise對象,狀態(tài)看執(zhí)行哪個回調(diào)函數(shù)決定。注意這是一個新對象,不是簡單把調(diào)用then的Promise對象拿來改裝后返回:

var aPromise = new Promise((resolve)=> resolve(100));
var thenPromise = aPromise.then((value)=> console.log(value));
var catchPromise = thenPromise.catch((error)=> console.error(error));
/* true */
console.log(aPromise !== thenPromise);
/* true */
console.log(thenPromise !== catchPromise);
鏈?zhǔn)秸{(diào)用

知道了then方法的具體細(xì)節(jié)后,我們就能明白Promise調(diào)用鏈上:

傳遞數(shù)據(jù)的方法:利用上面提到的then回調(diào)的參數(shù)傳遞形式——不論是在Promise對象產(chǎn)生過程中直接傳遞、還是在then回調(diào)返回值中間接傳遞——就能實現(xiàn)將每一級異步操作的結(jié)果傳遞給后續(xù)then中注冊的處理函數(shù)處理。

Promise對象狀態(tài)傳遞和改變的方法:利用then回調(diào)的返回值,可以控制某個操作后then方法返回的Promise對象及其狀態(tài)。

現(xiàn)在我們把所有異步操作改為Promise語法,再利用在Promise調(diào)用鏈傳遞數(shù)據(jù)和控制狀態(tài)的方法,就能把本節(jié)開始提到的預(yù)定房間操作中的回調(diào)嵌套都展開來了:

let promiseValidate = new Promise((resolve, reject)=> {
    validate(data, (err)=> {
        if (err) return reject(err);
        resolve();
        });
    });

let promiseRequest = new Promise((resolve, reject)=> {
    request(data, (err, apiResponse)=> {
        if (err) return reject(err);
        // 在Promise對象產(chǎn)生過程中直接傳遞異步操作的結(jié)果
        resolve(apiResponse);
        });
    }
);

let promiseInsertToDB = new Promise((resolve, reject)=> {
    insertToDB(data, (err)=> {
        if (err) return reject(err);
        resolve();
        });
    }
);

promiseValidate(data)
    .then(()=> promiseRequest(apiUrl))
    .then((apiResponse)=> {
        // 控制then回調(diào)的返回值,來改變then方法返回的新Promise對象的狀態(tài)
        if (apiResponse.isSuccessful) return insertToDB(data);
        else errorHandler(new Error("API error"));
        })
    .then(()=> successHandler())
    .catch((err)=> return errorHandler(err));

上面的代碼不僅將嵌套的代碼展開,讓我們掙脫了“回調(diào)地獄”;而且可以對異步操作的錯誤直接利用統(tǒng)一的Promise錯誤處理方法,避免寫一堆重復(fù)的代碼。如果要進(jìn)一步DRY,可以抽象出一個將典型的Node.js回調(diào)接口封裝為Promise接口的函數(shù):

/* 處理形如 receiver.fn(...args, (err, res)=> {}) 的接口 */
let promisify = (fn, receiver) => {
  return (...args) => { // 返回重新封裝的Promise接口
    return new Promise((resolve, reject) => {
      fn.apply(receiver, [...args, (err, res) => { // 重新綁定this
        return err ? reject(err) : resolve(res);
      }]);
    });
  };
};

/* 用例 */
let promiseValidate = promisify(validate, global);
let promiseRequest = promisify(request, global);
let promiseInsertToDB = promisify(insertToDB, global);

注意,由于resolve和reject方法只能接收一個參數(shù),所上面這個函數(shù)處理的回調(diào)里只能有err和一個數(shù)據(jù)參數(shù)。

Promise調(diào)用鏈上的錯誤處理

在Promise調(diào)用鏈上的處理錯誤的思路,就是去觸發(fā)Promise對象的Rejected狀態(tài),利用狀態(tài)的傳遞特性實現(xiàn)對錯誤的捕獲,再在catchthen回調(diào)里處理這些錯誤。下面我們就來進(jìn)行相關(guān)的探討:

錯誤的捕獲

首先我們有必要詳細(xì)了解下Promise對象的Rejected狀態(tài)的產(chǎn)生和傳遞過程。

Rejected狀態(tài)的產(chǎn)生有兩種情況:

調(diào)用了reject函數(shù):Promise對象實例化的回調(diào)調(diào)用了reject(),或者直接調(diào)用了Promise.reject()

通過throw拋出錯誤

而只要產(chǎn)生了Rejected狀態(tài),就會在調(diào)用鏈上持續(xù)傳遞,直到遇見Rejected狀態(tài)的處理回調(diào)(catch的回調(diào)或then的第二個回調(diào))。再結(jié)合之前提到的Promise調(diào)用鏈上的數(shù)據(jù)傳遞方法,錯誤就能在調(diào)用鏈上作為參數(shù)被相應(yīng)的回調(diào)“捕獲”了。這個過程可以參見下圖:

這里要注意,通過throw拋出錯時,如果錯誤是在setTimeout等的回調(diào)中拋出,是不會讓Promise對象產(chǎn)生Rejected狀態(tài)的,這也以為著Promise調(diào)用鏈上捕獲不了這個錯誤。舉個例子,下面這段代碼就不會有任何輸出:

Promise.resolve()
    .then(()=> setTimeout(100, ()=> {throw new Error("hi")}))
    .catch((err)=> console.log(err));

究其原因,是因為setTimeout的異步操作和Promise的異步操作不屬于同一種任務(wù)隊列,setTimeout回調(diào)里的錯誤會直接拋到全局變成Uncaught Error,而不會作用到Promise對象及其調(diào)用鏈上。這就也意味著,想要保證在調(diào)用鏈上產(chǎn)生的錯誤能被捕獲,就必須始終使用調(diào)用reject函數(shù)的方式來產(chǎn)生和傳遞錯誤。

錯誤處理

錯誤處理可以在catch的回調(diào)或then的第二個回調(diào)里進(jìn)行。雖然前面提到catch方法等價于then(undefined, Rejected狀態(tài)處理函數(shù)),但推薦始終使用catch來處理錯誤,原因有兩個:

代碼的可讀性

對于then(Fullfilled狀處理函數(shù), Rejected狀態(tài)的處理函數(shù))這種寫法,如果Fullfilled狀態(tài)的處理函數(shù)里出錯了,那錯誤只會繼續(xù)向下傳遞,同級的Rejected狀態(tài)處理函數(shù)沒辦法捕獲該錯誤

優(yōu)化房間預(yù)訂例子的錯誤處理

了解完了Promise調(diào)用鏈上的錯誤處理,我們再來回顧一開始提到的房間預(yù)訂例子。之前我們的代碼里只是對異步操作中的可能出現(xiàn)錯誤進(jìn)行了統(tǒng)一的處理,但是其中的API error等別的執(zhí)行錯誤并未使用在Promise調(diào)用鏈上捕獲和處理錯誤的方式。為了進(jìn)一步DRY,我們可以通過調(diào)用Promise.reject,強(qiáng)制將返回的Promise對象變?yōu)镽ejected狀態(tài),共用統(tǒng)一的Promise錯誤處理:

(apiResponse)=> {
        if (apiResponse.isSuccessful) return insertToDB(data);
        // 返回的Promise對象為Rejected狀態(tài),共用統(tǒng)一的Promise錯誤處理
        else return Promise.reject(new Error("API error"));
        }
Promise.all和Promise.race

前面研究的多個異步操作間往往具有前后依賴關(guān)系,或者說它們是“串行”進(jìn)行的,只有前一個完成了才能進(jìn)行后一個。但有時我們處理的異步操作間可能并不具有依賴關(guān)系,比如處理多張圖片,這時再使用上面的調(diào)用鏈寫法,就只能等處理完一張圖片、對應(yīng)的Promise對象狀態(tài)變化了,才能再去處理下一張,就顯得很低效了。所以,我們需要一種能在調(diào)用鏈中同時處理多個Promise對象的方法,Promise.allPromise.race就是這樣應(yīng)運而生的。

這兩個方法的相同點是會接受一個Promise對象組成的數(shù)組作為參數(shù),包裝返回成一個新的Promise實例。而它們的區(qū)別就在于返回的這個Promise實例狀態(tài)如何變化:

Promise.all

所有傳入的Promise對象狀態(tài)都變成Fullfilled,最終狀態(tài)才會變成Fullfilled;此時便會調(diào)用Promise.resolve(各Promise對象resolve參數(shù)組成的數(shù)組),生成新狀態(tài)的Promise對象返回

各個Promise對象若有一個被reject,最終狀態(tài)就變成Rejected;此時便會調(diào)用Promise.reject(第一個被reject的實例的reject參數(shù)),生成新狀態(tài)的Promise對象返回

Promise.race:只要傳入的各個Promise對象中有一個率先改變狀態(tài)(Fullfilled或Rejected),返回的Promise對象狀態(tài)就會改變?yōu)橄鄳?yīng)狀態(tài)

有了這兩個方法,我們就能在Promise調(diào)用鏈上“并行”等待某些異步操作了,還是用前面提到的客房例子來舉例,如果我們在預(yù)定房間時需要請求的API不止一個,調(diào)用鏈可以這么寫:

promiseValidate(data)
    /* 請求多個API */
    .then(()=> Promise.all([promiseRequest(apiUrl1), promiseRequest(apiUrl2)]))
    .then((apiResponse)=> {
        /* 傳給下個then回調(diào)的是一個resolve參數(shù)組成的數(shù)組 */
        if (apiResponse[0].isSuccessful && apiResponse[1].isSuccessful) return insertToDB(data);
        else return Promise.reject(new Error("API error"));
        })
    .then(()=> successHandler())
    .catch((err)=> return errorHandler(err));
Promise的應(yīng)用

Promise是一種異步調(diào)用的寫法,自然是用來寫出清晰的異步代碼、讓我們擺脫回調(diào)寫法帶來的種種弊端,本文一直使用的預(yù)定房間例子就是一個佐證。不過考慮實際的應(yīng)用場景,還是有一些需要注意的地方:

前端異步處理

前端的瀏覽器兼容性是阻礙新技術(shù)運用的一大難題,雖然使目前瀏覽器對于ES6的支持越來越完善了,但除非你不考慮IE(兼容性表),否則在前端代碼里直接使用的原生的Promise實現(xiàn)并不太現(xiàn)實。對于這種情況,我們可以用一些Polyfill或拓展類庫來讓我們能寫Promise代碼。

Node的異步處理:

Node.js環(huán)境下對ES6的Promise支持,在零點幾版開始就有了,所以我們在編寫服務(wù)器代碼、或者寫一些跑在Node上的模塊時可以直接上Promise語法。不過要注意的是,Node上的大部分模塊開放的API,還是默認(rèn)使用回調(diào)風(fēng)格,這是為了方便用戶在不了解Promise語法時快速上手;所以一般自己寫的模塊API也會遵循這個慣例,至于模塊內(nèi)部實現(xiàn)那就隨你的意愿使用了。

還有一個要值得注意的是,最近Node實現(xiàn)了更優(yōu)雅的異步寫法--async函數(shù),不過新的寫法是基于Promise實現(xiàn)的,所以雖然async函數(shù)的出現(xiàn)讓Promise有種高不成低不就的感覺,但了解Promise的用法還是很有必要的,希望本文能幫你做到這點:D。

參考

JavaScript Promise迷你書
Promise 的鏈?zhǔn)秸{(diào)用與中止
如何把 Callback 接口包裝成 Promise 接口

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/82835.html

相關(guān)文章

  • js學(xué)習(xí)之異步處理

    摘要:學(xué)習(xí)開發(fā),無論是前端開發(fā)還是都避免不了要接觸異步編程這個問題就和其它大多數(shù)以多線程同步為主的編程語言不同的主要設(shè)計是單線程異步模型。由于異步編程可以實現(xiàn)非阻塞的調(diào)用效果,引入異步編程自然就是順理成章的事情了。 學(xué)習(xí)js開發(fā),無論是前端開發(fā)還是node.js,都避免不了要接觸異步編程這個問題,就和其它大多數(shù)以多線程同步為主的編程語言不同,js的主要設(shè)計是單線程異步模型。正因為js天生的與...

    VioletJack 評論0 收藏0
  • 【16】winter重學(xué)前端 - JavaScript執(zhí)行():Promise里的代碼為什么比se

    摘要:即使耗時一秒的執(zhí)行完畢,再的,仍然先于執(zhí)行了,這很好地解釋了微任務(wù)優(yōu)先的原理。把整個代碼分割成了個宏觀任務(wù),這里不論是秒還是秒,都是一樣的。 js實現(xiàn)異步的幾種形式 回調(diào)函數(shù) 事件監(jiān)聽 - 事件驅(qū)動模式 發(fā)布/訂閱 - 觀察者模式 Promises對象 js異步歷史 一個 JavaScript 引擎會常駐于內(nèi)存中,它等待著我們把JavaScript 代碼或者函數(shù)傳遞給它執(zhí)行 在 ...

    Vicky 評論0 收藏0
  • 學(xué)前端學(xué)習(xí)筆記(十七)--Promise里的代碼為什么比setTimeout先執(zhí)行?

    摘要:版本以及之前,本身還沒有異步執(zhí)行代碼的能力,宿主環(huán)境傳遞給引擎,然后按順序執(zhí)行,由宿主發(fā)起任務(wù)。采納引擎術(shù)語,把宿主發(fā)起的任務(wù)稱為宏觀任務(wù),把引擎發(fā)起的任務(wù)稱為微觀任務(wù)。基本用法示例的回調(diào)是一個異步的執(zhí)行過程。 筆記說明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時間開的一個專欄,每天10分鐘,重構(gòu)你的前端知識體系,筆者主要整理學(xué)習(xí)過程的一些要點筆記以及感悟,完整的...

    pinecone 評論0 收藏0
  • 學(xué)前端學(xué)習(xí)筆記(十七)--Promise里的代碼為什么比setTimeout先執(zhí)行?

    摘要:版本以及之前,本身還沒有異步執(zhí)行代碼的能力,宿主環(huán)境傳遞給引擎,然后按順序執(zhí)行,由宿主發(fā)起任務(wù)。采納引擎術(shù)語,把宿主發(fā)起的任務(wù)稱為宏觀任務(wù),把引擎發(fā)起的任務(wù)稱為微觀任務(wù)。基本用法示例的回調(diào)是一個異步的執(zhí)行過程。 筆記說明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時間開的一個專欄,每天10分鐘,重構(gòu)你的前端知識體系,筆者主要整理學(xué)習(xí)過程的一些要點筆記以及感悟,完整的...

    zorpan 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<