你們好,我是IT修真院武漢分院第14期學員,一枚正直善良的web程序員。javascript
今天給你們分享一下,修真院官網JS-9任務中什麼是promisehtml
1、背景介紹
什麼是promise?java
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。git
所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。程序員
2、知識剖析
一、Promise的基本用法es6
ES6 規定,Promise對象是一個構造函數,用來生成Promise實例。下面代碼創造了一個Promise實例。github
var p = new Promise(function (resolve, reject) { // ... some code if (/* 異步操做成功 */) { resolve(value); } else { reject(error); } });
二、resolve和rejectweb
Promise構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolve和reject。編程
它們是兩個函數,由JavaScript引擎提供,不用本身部署。數組
resolve函數的做用是,將Promise對象的狀態從「未完成」變爲「成功」(即從Pending變爲
Resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;reject函數的做用是,
將Promise對象的狀態從「未完成」變爲「失敗」(即從Pending變爲
Rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。
Promise實例生成之後,能夠用then方法分別指定Resolved狀態和Rejected狀態的回調函數。
p.then(function (value) { // success }, function (error) { // failure });
3、常見問題
如何使用Promise?
4、解決方案
一、用法以下:
須要注意的是,new一個Promise對象時,裏面的代碼就直接運行了。
因此咱們用Promise的時候通常是包在一個函數中,在須要的時候去運行這個函數。
function runAsync(){ var p = new Promise(function(resolve, reject){ //作一些異步操做 setTimeout(function(){ console.log('執行完成'); resolve('隨便什麼數據'); }, 2000); }); return p; } runAsync().then(function(data){ console.log(data); //後面能夠用傳過來的數據作些其餘操做 //...... });
若是不用Promise而是用回調函數去寫:
then裏面的函數就跟咱們平時的回調函數一個意思,可以在runAsync這個 異步任務執行完成以後被執行。這就是Promise的做用了,簡單來說,就是能把 原來的回調寫法分離出來,在異步操做執行完後,用鏈式調用的方式執行回調函數。 寫成回調函數以下:
//回調函數寫法 function runAsync(callback) { setTimeout(function () { console.log('執行完成'); callback('隨便什麼數據'); }, 2000); }; runAsync(function (data) { console.log(data); });
二、關於事件循環和任務隊列
講事件循環和任務隊列以前,先講一下我發現的setTimeout機制
//下面看一下setTime函數機制 例1 setTimeout(function () {//給延時函數設置爲0,但願它當即運行 console.log(1); }, 0); console.log(2);
你以爲先輸出哪一個。
看上去好像是先輸出1,再輸出2.實際上卻不是這樣的。其實是先1,再2.爲何呢?
由於JS單線程的事件循環和任務隊列。
下面給你們科普一下關於任務隊列。
(1)全部同步任務都在主線程上執行,造成一個執行棧。
(2)主線程以外,還存在一個"任務隊列"。任務隊列又分爲macrotask和microtask。任務隊列能夠有多個。
(3)在主線程執行棧執行空以後,會先讀取全部的微觀隊列,而後再讀取一個宏觀隊列。再讀取全部的微觀隊列。
macrotasks: script(總體代碼),setTimeout, setInterval,Ajax
microtasks: Promise
瞭解他們的關係以後,咱們再來看一個例子
console.log(1); setTimeout(function () { console.log(2);//setTimeout在宏觀任務隊列 }, 0); new Promise(function (resolve) { console.log(3);//異步第一部分是屬於執行棧 resolve(); console.log(4); }).then(function () { console.log(5);//回調的函數在微觀任務隊列 }); console.log(6);
想想輸出數字1-6的輸出順序是怎麼樣的?若是能答上來,說明你大體仍是理解了上面介紹的事件循環和任務隊列。
答案請先本身思考下,否則我是不會告訴你答案在文章最後面的。
三、接下來繼續講promise和回調函數兩種寫法的區別
那麼問題來了,既然回調函數回調函數能實現異步操做,爲何還要用Promise呢?
請考慮這種狀況:有多層回調該怎麼辦?若是callback也是一個異步操做,並且執行 完後也須要有相應的回調函數,該怎麼辦呢?總不能再定義一個callback2,而後給 callback傳進去吧。而Promise的優點在於,能夠在then方法中繼續寫Promise對 象並返回,而後繼續調用then來進行回調操做。
四、PROMISE的鏈式調用
因此,從表面上看,Promise只是可以簡化層層回調的寫法,而實質上,Promise的精髓是「狀態」, 用維護狀態、傳遞狀態的方式來使得回調函數可以及時調用,它比傳遞callback函數要簡單、靈活的多。 因此使用Promise的正確場景是這樣的:
runAsync1() .then(function (data) { for (i = 0; i < 1000000; i++) { i = i - 0.5; } console.log(data, new Date() - start, "ms"); //當前時間減開始時間 return runAsync2(); }) .then(function (data) { for (i = 0; i < 1000000; i++) { i = i - 0.5; } console.log(data, new Date() - start, "ms"); //當前時間減開始時間 return runAsync3(); // return '直接返回數據'; }) .then(function (data) { for (i = 0; i < 1000000; i++) { i = i - 0.5; } console.log(data, new Date() - start, "ms"); //當前時間減開始時間 });
runAsync一、runAsync二、runAsync3這三個函數的定義:
var start = new Date(); function runAsync1() { var p = new Promise(function (resolve, reject) { for (i = 0; i < 1000000; i++) { i = i - 0.5; } console.log('異步任務1執行完成:', new Date() - start, "ms"); resolve('回調函數1'); }); return p; } function runAsync2() { var p = new Promise(function (resolve, reject) { for (i = 0; i < 1000000; i++) { i = i - 0.5; } console.log('異步任務2執行完成:', new Date() - start, "ms"); resolve('回調函數2'); }); return p; } function runAsync3() { var p = new Promise(function (resolve, reject) { for (i = 0; i < 1000000; i++) { i = i - 0.5; } console.log('異步任務3執行完成:', new Date() - start, "ms"); resolve('回調函數3'); }); return p; }
結果爲:
for循環運行事件大體在130ms左右。因此每一步都大概這麼多,這就是promise的鏈式。
在then方法中,你也能夠直接return數據而不是Promise對象,在後面的then中就能夠接收到數據了,好比咱們把上面的代碼修改爲這樣:
runAsync1() .then(function (data) { for (i = 0; i < 1000000; i++) { i = i - 0.5; } console.log(data, new Date() - start, "ms"); //當前時間減開始時間 return runAsync2(); }) .then(function (data) { for (i = 0; i < 1000000; i++) { i = i - 0.5; } console.log(data, new Date() - start, "ms"); //當前時間減開始時間 return '直接返回數據'; }) .then(function (data) { for (i = 0; i < 1000000; i++) { i = i - 0.5; } console.log(data, new Date() - start, "ms"); //當前時間減開始時間 });
這樣輸出的結果是:
能夠看到異步任務3沒有執行了,由於返回的是數據。可是後面的then仍是會執行,打印出"直接返回數據"
五、REJECT的用法
事實上,咱們前面的例子都是隻有「執行成功」的回調,尚未「失敗」的狀況, reject的做用就是把Promise的狀態置爲rejected,這樣咱們在then中就 能捕捉到,而後執行「失敗」狀況的回調。看下面的代碼。
//reject用法 function getNumber() { var p = new Promise(function (resolve, reject) { //作一些異步操做 setTimeout(function () { var num = Math.ceil(Math.random() * 10); //生成1-10的隨機數 if (num <= 5) { reject('數字過小了'); } else { resolve(num); } }, 1000); }); return p; } getNumber() .then( function (data) { console.log('resolved'); console.log(data); }, function (reason, data) { console.log('rejected'); console.log(reason); } );
七、CATCH的用法
咱們知道Promise對象除了then方法,還有一個catch方法,它是作什麼用的呢? 其實它和then的第二個參數同樣,用來指定reject的回調,用法是這樣:
//catch回調失敗 getNumber() .then(function (data) { console.log('resolved'); console.log(data); }) .catch(function (reason) { console.log('rejected'); console.log(reason); });
結果:
或
上面代碼的效果和寫在then的第二個參數裏面同樣。不過它還有另一個做用:在執行resolve的回調 (也就是上面then中的第一個參數)時,若是拋出異常了(代碼出錯了),那麼並不會報錯 卡死js,而是會進到這個catch方法中。請看下面的代碼:
getNumber() .then(function (data) { console.log('resolved'); console.log(data); console.log(somedata); //此處的somedata未定義 }) .catch(function (reason) { console.log('reject'); console.log(reason); });
也就是說進到catch方法裏面去了,並且把錯誤緣由傳到了reason參數中。 即使是有錯誤的代碼也不會報錯了,這與咱們的try/catch語句有相同的功能。
結果爲:
能夠看到每次運行都是catch結果,並且未報錯,是由於直接進入catch。打印catch裏的內容。
5、編碼實戰
見視頻或者ppt
6、拓展思考
一、all的用法
Promise的all方法提供了並行執行異步操做的能力,而且在全部異步操做執行完後才執行回調。 咱們仍舊使用上面定義好的runAsync一、runAsync二、runAsync3這三個函數,看下面的例子:
//拓展思考之all Promise .all([runAsync1(), runAsync2(), runAsync3()]) .then(function (results) { console.log(results); });
用Promise.all來執行,all接收一個數組參數,裏面的值最終都 算返回Promise對象。這樣,三個異步操做的並行執行的,等到它 們都執行完後纔會進到then裏面。那麼,三個異步操做返回的數據哪裏 去了呢?都在then裏面呢,all會把全部異步操做的結果放進一個數組中 傳給then,就是上面的results
有了all,你就能夠並行執行多個異步操做,而且在一個回調中處理全部的返回數 據,是否是很酷?有一個場景是很適合用這個的,一些遊戲類的素材比較多的應用, 打開網頁時,預先加載須要用到的各類資源如圖片、flash以及答案134652各類靜態文件。所 有的都加載完後,咱們再進行頁面的初始化。
二、race的用法
all方法的效果其實是「誰跑的慢,以誰爲準執行回調」, 那麼相對的就有另外一個方法「誰跑的快,以誰爲準執行回調」, 這就是race方法,這個詞原本就是賽跑的意思。race的用法 與all同樣,看一下下面的代碼:
Promise .race([runAsync1(), runAsync2(), runAsync3()]) .then(function (results) { console.log(results); });
在then裏面的回調開始執行時,runAsync2()和runAsync3()並無中止, 仍舊再執行。因而再過1秒後,輸出了他們結束的標誌。
這個race有什麼用呢?使用場景仍是不少的,好比咱們能夠用race給某個異步請求 設置超時時間,而且在超時後執行相應的操做,代碼以下:
//請求某個圖片資源 function requestImg(){ var p = new Promise(function(resolve, reject){ var img = new Image(); img.onload = function(){ resolve(img); } img.src = './img/world.jpg'; }); return p; } //延時函數,用於給請求計時 function timeout(){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ reject('圖片請求超時'); }, 2); }); return p; } Promise .race([requestImg(), timeout()]) .then(function(results){ console.log(results); }) .catch(function(reason){ console.log(reason); });
requestImg函數會異步請求一張圖片,我把地址寫爲"xxxxxx",因此確定是無 法成功請求到的。timeout函數是一個延時2ms的異步操做。咱們把這兩個返回 Promise對象的函數放進race,因而他倆就會賽跑,若是2ms以內圖片請求成功了, 那麼遍進入then方法,執行正常的流程。若是2ms圖片還未成功返回,那麼timeout 就跑贏了,則進入catch,報出「圖片請求超時」的信息。
7、參考文獻
大白話講解Promise(一)
也能夠看個人視頻,有較爲通俗易懂的講解:
https://v.qq.com/x/page/n0671...
8、更多討論
Q1:reject用法中,funtion1和funtion2可否對調位置?
A1:不行,沒有catch的話,默認第一個funtion是接受成功返回值,第二個接受失敗返回值。
Q2:callback的異步機制?
A2:callback會造成一個單獨的callback queue。一個宏觀隊列。
Q3:Ajax異步機制
A3: 請求是由瀏覽器新開一個線程請求(見前面的瀏覽器多線程)。當請求的狀態變動時,若是先前已設置回調,這異步線程就產生狀態變動 事件放到 JavaScript引擎的事件處理隊列中等待處理。當瀏覽器空閒的時候出隊列任務被處理,JavaScript引擎始終是單線程運行回調函數。 javascript引擎確實是單線程處理它的任務隊列,能理解成就是普通函數和回調函數構成的隊列