前端開發中常常會進行一些異步操做,常見的異步有:javascript
博客地址php
最基礎的異步解決方案莫過於回調函數了html
前端常常會在成功時和失敗時分別註冊回調函數前端
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,則一般只註冊一個回調函數,經過約定的參數來判斷究竟是成功仍是失敗:java
const fs = require("fs"); fs.readFile('input.txt', function (err, data) { // 回調函數 // 第一個參數是err,若是有err,則表示調用失敗 if (err) { return console.error(err); } console.log("異步讀取: " + data.toString()); });
回調的異步解決方案自己也簡單易懂,可是它有一個致命的缺點:沒法優雅的控制異步流程node
什麼意思?git
單個異步固然能夠很簡單的使用回調函數,可是對於多個異步操做,就會陷入回調地獄中es6
// 請求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); } }) } }) } })
這種要按順序進行異步流程控制的場景,回調函數就顯得捉襟見肘了。這時,Promise的異步解決方案就被提了出來。github
當初在學Promise時,看得我真是一臉懵逼,徹底不明白這貨到底怎麼用。其實,Promise的api要分紅兩部分來理解:ajax
Promise對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)
初始時,該對象狀態爲pending,以後只能變成fulfilled和rejected其中的一個
then方法有兩個參數,分別對應狀態爲fulfilled和rejected時的回調函數,其中第二個參數可選
promise.then(function(value) { // success }, function(error) { // failure });
一般咱們會省略then的第二個參數,而改用catch來註冊狀態變爲rejected時的回調函數
promise.then(function(value) { // success }).catch(function(error) { // failure });
Promise對象怎麼生成的呢?就是經過構造函數new出來的。
const promise = new Promise(function(resolve, reject) { });
Promise構造函數接收一個函數做爲參數,這個函數能夠接收兩個參數:resolve和reject
resolve, reject是兩個函數,由JavaScript引擎提供,不用本身編寫
前面咱們說過,Promise對象有三種狀態,初始時爲pending,以後能夠變成fulfilled或者rejected,那怎麼改變狀態呢?答案就是調用resolve或者reject
調用resolve時,狀態變成fulfilled,表示異步已經完成;調用reject時,狀態變成rejected,表示異步失敗。
其實這裏就是Promise最難理解的地方了,咱們先看下例子:
回調函數封裝
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); });
兩段代碼最大的區別就是:
用回調函數封裝的getURL函數,須要明顯的傳給它成功和失敗的回調函數,success和error的最終調用是在getURL裏被調用的
用Promise封裝的getURL函數,徹底不關心成功和失敗的回調函數,它只須要在ajax成功時調用resolve(),告訴promise對象,你如今的狀態變成了fulfilled,在ajax失敗時,調用reject()。而真正的回調函數,是在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對於流程控制的優點:讀取URL的數據後再讀取URL2,沒有了以前的回調地獄問題。
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 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.all 接收一個promise對象的數組做爲參數,當這個數組裏的全部promise對象所有變爲resolve的時候,它纔會去調用then方法,若是其中有一個變爲rejected,就直接調用catch方法
傳給then方法的是一個數組,裏面分別對應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(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 });
理想狀態下,Promise能夠經過catch捕獲到異常,可是若是咱們沒有使用catch,那麼雖然控制檯會打印錯誤,可是此次錯誤並不會終止腳本執行
<script> const a = b.c.d; console.log(1); // 代碼報錯,不會運行到此處 </script> <script> console.log(2); // 代碼運行 </script>
上述代碼只會打印2
<script> const promise = new Promise((resolve, reject) => { const a = b.c.d; resolve('ok'); }) promise.then(data => { console.log(data) }) console.log(1); // 代碼報錯,可是會運行到此處 </script> <script> console.log(2); // 代碼運行 </script>
打印1和2
解決方法:
window有一個unhandledRejection事件,專門監聽未捕獲的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) })