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