摘要:而真正的回調(diào)函數(shù),是在的外面被調(diào)用的,也就是和中調(diào)用方法返回的是一個(gè)新的實(shí)例注意,不是原來那個(gè)實(shí)例。
前端開發(fā)中經(jīng)常會(huì)進(jìn)行一些異步操作,常見的異步有:
網(wǎng)絡(luò)請(qǐng)求:ajax
IO操作: readFile
定時(shí)器:setTimeout
博客地址
回調(diào)最基礎(chǔ)的異步解決方案莫過于回調(diào)函數(shù)了
前端經(jīng)常會(huì)在成功時(shí)和失敗時(shí)分別注冊(cè)回調(diào)函數(shù)
const req = new XMLHttpRequest(); req.open("GET", URL, true); req.onload = function () { // 成功的回調(diào) if (req.status === 200) { console.log(req.statusText) } }; req.onerror = function () { // 失敗的回調(diào) console.log(req.statusText) }; req.send();
node的異步api,則通常只注冊(cè)一個(gè)回調(diào)函數(shù),通過約定的參數(shù)來判斷到底是成功還是失敗:
const fs = require("fs"); fs.readFile("input.txt", function (err, data) { // 回調(diào)函數(shù) // 第一個(gè)參數(shù)是err,如果有err,則表示調(diào)用失敗 if (err) { return console.error(err); } console.log("異步讀取: " + data.toString()); });
回調(diào)的異步解決方案本身也簡(jiǎn)單易懂,但是它有一個(gè)致命的缺點(diǎn):無法優(yōu)雅的控制異步流程
什么意思?
單個(gè)異步當(dāng)然可以很簡(jiǎn)單的使用回調(diào)函數(shù),但是對(duì)于多個(gè)異步操作,就會(huì)陷入回調(diào)地獄中
// 請(qǐng)求data1成功后再請(qǐng)求data2,最后請(qǐng)求data3 const ajax = $.ajax({ url: "data1.json", success: function(data1) { console.log(data1); $.ajax({ url: "data2.json", success: function(data2) { console.log(data2); $.ajax({ url: "data3.json", success: function(data3) { console.log(data3); } }) } }) } })
這種要按順序進(jìn)行異步流程控制的場(chǎng)景,回調(diào)函數(shù)就顯得捉襟見肘了。這時(shí),Promise的異步解決方案就被提了出來。
Promise當(dāng)初在學(xué)Promise時(shí),看得我真是一臉懵逼,完全不明白這貨到底怎么用。其實(shí),Promise的api要分成兩部分來理解:
Promise構(gòu)造函數(shù):resolve reject (改變內(nèi)部狀態(tài))
Promise對(duì)象: then catch (流程控制)
Promise對(duì)象Promise對(duì)象代表一個(gè)異步操作,有三種狀態(tài):pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失?。?/p>
初始時(shí),該對(duì)象狀態(tài)為pending,之后只能變成fulfilled和rejected其中的一個(gè)
then方法有兩個(gè)參數(shù),分別對(duì)應(yīng)狀態(tài)為fulfilled和rejected時(shí)的回調(diào)函數(shù),其中第二個(gè)參數(shù)可選
promise.then(function(value) { // success }, function(error) { // failure });
通常我們會(huì)省略then的第二個(gè)參數(shù),而改用catch來注冊(cè)狀態(tài)變?yōu)閞ejected時(shí)的回調(diào)函數(shù)
promise.then(function(value) { // success }).catch(function(error) { // failure });Promise構(gòu)造函數(shù)
Promise對(duì)象怎么生成的呢?就是通過構(gòu)造函數(shù)new出來的。
const promise = new Promise(function(resolve, reject) { });
Promise構(gòu)造函數(shù)接收一個(gè)函數(shù)作為參數(shù),這個(gè)函數(shù)可以接收兩個(gè)參數(shù):resolve和reject
resolve, reject是兩個(gè)函數(shù),由JavaScript引擎提供,不用自己編寫
前面我們說過,Promise對(duì)象有三種狀態(tài),初始時(shí)為pending,之后可以變成fulfilled或者rejected,那怎么改變狀態(tài)呢?答案就是調(diào)用resolve或者reject
調(diào)用resolve時(shí),狀態(tài)變成fulfilled,表示異步已經(jīng)完成;調(diào)用reject時(shí),狀態(tài)變成rejected,表示異步失敗。
回調(diào)和Promise的對(duì)比其實(shí)這里就是Promise最難理解的地方了,我們先看下例子:
回調(diào)函數(shù)封裝
function getURL(URL, success, error) { const req = new XMLHttpRequest(); req.open("GET", URL, true); req.onload = function () { if (req.status === 200) { success(req.responseText); } else { error(new Error(req.statusText)); } }; req.onerror = function () { error(new Error(req.statusText)); }; req.send(); } const URL = "http://httpbin.org/get"; getURL(URL, function onFulfilled(value) { console.log(value); }, function onRejected(error) { console.error(error); })
Promise封裝
function getURL(URL) { return new Promise(function (resolve, reject) { const req = new XMLHttpRequest(); req.open("GET", URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } const URL = "http://httpbin.org/get"; getURL(URL).then(function onFulfilled(value){ console.log(value); }).catch(function onRejected(error){ console.error(error); });
兩段代碼最大的區(qū)別就是:
用回調(diào)函數(shù)封裝的getURL函數(shù),需要明顯的傳給它成功和失敗的回調(diào)函數(shù),success和error的最終調(diào)用是在getURL里被調(diào)用的
用Promise封裝的getURL函數(shù),完全不關(guān)心成功和失敗的回調(diào)函數(shù),它只需要在ajax成功時(shí)調(diào)用resolve(),告訴promise對(duì)象,你現(xiàn)在的狀態(tài)變成了fulfilled,在ajax失敗時(shí),調(diào)用reject()。而真正的回調(diào)函數(shù),是在getURL的外面被調(diào)用的,也就是then和catch中調(diào)用
then方法返回的是一個(gè)新的Promise實(shí)例(注意,不是原來那個(gè)Promise實(shí)例)。因此可以采用鏈?zhǔn)綄懛?,即then方法后面再調(diào)用另一個(gè)then方法。
function getURL(URL) { return new Promise(function (resolve, reject) { const req = new XMLHttpRequest(); req.open("GET", URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } const URL = "http://httpbin.org/get"; const URL2 = "http://deepred5.com/cors.php?search=ntr"; getURL(URL).then(function onFulfilled(value){ console.log(value); // 返回了一個(gè)新的Promise對(duì)象 return getURL(URL2) }).then(function onFulfilled(value){ console.log(value); }).catch(function onRejected(error){ console.error(error); });
這段代碼就充分說明了Promise對(duì)于流程控制的優(yōu)勢(shì):讀取URL的數(shù)據(jù)后再讀取URL2,沒有了之前的回調(diào)地獄問題。
Promise應(yīng)用Promise經(jīng)常用于對(duì)函數(shù)的異步流程封裝
function getURL(URL) { return new Promise(function (resolve, reject) { const req = new XMLHttpRequest(); req.open("GET", URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); }
const preloadImage = function (path) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; }); };
const fs = require("fs") const path = require("path") const readFilePromise = function (fileName) { return new Promise((resolve, reject) => { fs.readFile(fileName, (err, data) => { if (err) { reject(err) } else { resolve(data.toString()) } }) }) }
結(jié)合上面幾個(gè)例子,我們可以看出Promise封裝代碼的基本套路:
const methodPromise = function() { return new Promise((resolve, reject) => { // 異步流程 if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } }) }Promise.race Promise.all
Promise.all 接收一個(gè)promise對(duì)象的數(shù)組作為參數(shù),當(dāng)這個(gè)數(shù)組里的所有promise對(duì)象全部變?yōu)閞esolve的時(shí)候,它才會(huì)去調(diào)用then方法,如果其中有一個(gè)變?yōu)閞ejected,就直接調(diào)用catch方法
傳給then方法的是一個(gè)數(shù)組,里面分別對(duì)應(yīng)promise返回的結(jié)果
function getURL(URL) { return new Promise(function (resolve, reject) { const req = new XMLHttpRequest(); req.open("GET", URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } Promise.all([getURL("http://deepred5.com/cors.php?search=ntr"), getURL("http://deepred5.com/cors.php?search=rbq")]) .then((dataArr) => { const [data1, data2] = dataArr; }).catch((err) => { console.log(err) })
Promise.race類似,只不過只要有一個(gè)Promise變成resolve就調(diào)用then方法
Promise.resolve Promise.rejectPromise.resolve(42); // 等價(jià)于 new Promise(function(resolve){ resolve(42); }); Promise.reject(new Error("出錯(cuò)了")) // 等價(jià)于 new Promise(function(resolve,reject){ reject(new Error("出錯(cuò)了")); });
Promise.resolve(42).then(function(value){ console.log(value); }); Promise.reject(new Error("出錯(cuò)了")).catch(function(error){ console.error(error); });
Promise.resolve方法另一個(gè)作用就是將thenable對(duì)象轉(zhuǎn)換為promise對(duì)象
const promise = Promise.resolve($.ajax("/json/comment.json"));// => promise對(duì)象 promise.then(function(value){ console.log(value); });
thenable對(duì)象指的是具有then方法的對(duì)象:
let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 });異常捕獲
理想狀態(tài)下,Promise可以通過catch捕獲到異常,但是如果我們沒有使用catch,那么雖然控制臺(tái)會(huì)打印錯(cuò)誤,但是這次錯(cuò)誤并不會(huì)終止腳本執(zhí)行
上述代碼只會(huì)打印2
打印1和2
解決方法:
window有一個(gè)unhandledRejection事件,專門監(jiān)聽未捕獲的reject錯(cuò)誤
window.onunhandledrejection = function(e) { console.log(e.reason); } const promise = new Promise((resolve, reject) => { const a = b.c.d; resolve("ok"); }) promise.then(data => { console.log(data) })參考
ECMAScript 6 入門
JavaScript Promise迷你書
深入理解 JavaScript 異步
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/96737.html
摘要:通過請(qǐng)求和響應(yīng)的交換達(dá)成通信協(xié)議中已經(jīng)規(guī)定了請(qǐng)求是從客戶端發(fā)出,最后由服務(wù)端響應(yīng)這個(gè)請(qǐng)求并返回。隨后的字符串指明了請(qǐng)求訪問的資源對(duì)象。協(xié)議自身不對(duì)請(qǐng)求和響應(yīng)之間的通信狀態(tài)進(jìn)行保存,也就是說這個(gè)級(jí)別。從前發(fā)送請(qǐng)求后需等待并受到響應(yīng)。 showImg(https://segmentfault.com/img/bVbmDsG?w=1024&h=538); http協(xié)議用戶客戶端和服務(wù)器之間的...
摘要:報(bào)文用于協(xié)議交互的信息被稱為報(bào)文?,F(xiàn)在出現(xiàn)的各種首部字段及狀態(tài)碼稍后會(huì)闡述。狀態(tài)碼響應(yīng)報(bào)文包含了多個(gè)范圍的內(nèi)容使用。如果服務(wù)器無法響應(yīng)范圍請(qǐng)求,則會(huì)返回狀態(tài)碼和完整的實(shí)體內(nèi)容。 showImg(https://segmentfault.com/img/bVbthNL?w=900&h=500); http報(bào)文 用于HTTP協(xié)議交互的信息被稱為HTTP報(bào)文。請(qǐng)求端的http報(bào)文叫做請(qǐng)求報(bào)文...
摘要:轉(zhuǎn)自前端外刊評(píng)論非常感謝,翻譯的很好,受益很多,轉(zhuǎn)到此處讓前端小伙伴們也驚呆下上次我寫前端工程師必知必會(huì)已經(jīng)是三年前了,那是我寫過最火的文章了。測(cè)試的第二大障礙是工具。 轉(zhuǎn)自:前端外刊評(píng)論 非常感謝,翻譯的很好,受益很多,轉(zhuǎn)到此處讓前端小伙伴們也驚呆下........ 上次我寫《前端工程師必知必會(huì)》已經(jīng)是三年前了,那是我寫過最火的文章了。三年了,我仍然會(huì)在Twitter上...
摘要:轉(zhuǎn)自前端外刊評(píng)論非常感謝,翻譯的很好,受益很多,轉(zhuǎn)到此處讓前端小伙伴們也驚呆下上次我寫前端工程師必知必會(huì)已經(jīng)是三年前了,那是我寫過最火的文章了。測(cè)試的第二大障礙是工具。 轉(zhuǎn)自:前端外刊評(píng)論 非常感謝,翻譯的很好,受益很多,轉(zhuǎn)到此處讓前端小伙伴們也驚呆下........ 上次我寫《前端工程師必知必會(huì)》已經(jīng)是三年前了,那是我寫過最火的文章了。三年了,我仍然會(huì)在Twitter上...
閱讀 2737·2021-09-23 11:21
閱讀 1963·2021-09-22 15:15
閱讀 1150·2021-09-10 11:27
閱讀 3515·2019-08-30 15:54
閱讀 724·2019-08-30 15:52
閱讀 1392·2019-08-30 15:44
閱讀 2417·2019-08-29 15:06
閱讀 3053·2019-08-28 18:21