有3種狀態:1、pending,表示正在進行中,該狀態會進行初始化;2、fulfilled,表示已成功;3、rejected,表示已失敗,會觸發后續的catch回調函數。promise的狀態發生改后就會凝固,不會再變,會一直保持這個結果。
本教程操作環境:windows7系統、ECMAScript 6版、Dell G3電腦。
Promise簡介
Promise 是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更合理和更強大。
所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。
從語法上說,Promise 是一個構造函數,從它可以獲取異步操作的消息。
Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。有了Promise對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。
Promise對象提供統一的接口,使得控制異步操作更加容易。
我們知道,es5是單線程語言,語句執行順序由上往下,而項目前端對接后端時,需要用到ajax,而ajax是異步的,可能導致數據的交互產生延遲,不利于編程。而promise函數可以很好的解決這個問題。
Promise實例化
Promise
構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve
和reject
。而這兩個參數是兩個函數,由 JavaScript 引擎提供。
Promise對象代表一個異步操作,有三種狀態: pending(進行中)、fulfilled(已成功)和rejected(已失敗)。
-
初始化,狀態:pending
-
當調用resolve(成功),狀態:pengding=>fulfilled
-
當調用reject(失敗),狀態:pending=>rejected
狀態發生改變之后就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。
狀態變化:
1、pending -> resolved
2、pending -> rejected
狀態的表現
-
pending狀態不會觸發then和catch
-
resolved狀態會觸發后續的then回調函數
-
rejected狀態會觸發后續的catch回調函數
then和catch改變狀態
-
then正常情況下會返回resolved,報錯則返回rejected
-
catch正常情況下會返回resolved,報錯則返回rejected
const promise = new Promise(function(resolve,reject){ //...some code if(/*異步操作成功*/){ resolve(value); // 狀態由pending變為fulfilled }else{ reject(error); // 狀態由pending變為rejected } })
例如:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>測試</title> </head> <body> <script> let promise = new Promise(function (resolve, reject) { if (3 < 5) { resolve("是正確的"); } else { reject("是錯誤的"); } }) console.log(promise); </script> </body> </html>
結果:
Promise的原型方法
定義在Promise.prototype
中的方法,通過Promise實例可以直接調用。
1、Promise.prototype.then()
當狀態由pending變為fulfilled的時候執行該回調函數,
參數:
最多需要有兩個參數,Promise 的成功和失敗情況的回調函數。
返回值:
返回一個新的Promise實例對象,因此可以使用鏈式調用。
當一個 Promise 完成(fulfilled)或者失敗(rejected)時,返回函數將被異步調用(由當前的線程循環來調度完成)。具體的返回值依據以下規則返回。如果 then 中的回調函數:
-
返回了一個值,那么 then 返回的 Promise 將會成為接受狀態,并且將返回的值作為接受狀態的回調函數的參數值。
-
沒有返回任何值,那么 then 返回的 Promise 將會成為接受狀態,并且該接受狀態的回調函數的參數值為 undefined。
-
throw拋出一個錯誤,那么 then 返回的 Promise 將會成為拒絕狀態,并且將拋出的錯誤作為拒絕狀態的回調函數的參數值。
-
返回一個已經是接受狀態的 Promise,那么 then 返回的 Promise 也會成為接受狀態,并且將那個 Promise 的接受狀態的回調函數的參數值作為該被返回的Promise的接受狀態回調函數的參數值。
-
返回一個已經是拒絕狀態的 Promise,那么 then 返回的 Promise 也會成為拒絕狀態,并且將那個 Promise 的拒絕狀態的回調函數的參數值作為該被返回的Promise的拒絕狀態回調函數的參數值。
-
返回一個未定狀態(pending)的 Promise,那么 then 返回 Promise 的狀態也是未定的,并且它的終態與那個 Promise 的終態相同;同時,它變為終態時調用的回調函數參數與那個 Promise 變為終態時的回調函數的參數是相同的。
將上面的規則簡單總結:
1、如果回調函數中的返回結果是promise對象,則對象狀態由回調函數的執行結果決定
2、如果回到函數中的返回結果為非promise對象(無論是字符串、undefined…只要不是promise對象),對象狀態均為成功,返回值為對象成功調用中的值。
3、throw拋出錯誤,狀態為rejected
let p1 = new Promise((resolve, reject) => { resolve('成功!'); // 或者 // reject(new Error("出錯了!")); }); p1.then(value => { console.log(value); // 成功! }, error => { console.log(error); // 出錯了! });
2、Promise.prototype.catch()
當狀態由pending變為rejected的時候執行該回調函數,
參數:
回調函數,回調函數的參數為reject函數傳遞過來的值
返回值:
返回一個新的Promise實例對象,因此可以使用鏈式調用。
// 拋出一個錯誤,大多數時候將調用catch方法 let p1 = new Promise(function(resolve, reject) { throw 'Uh-oh!'; }); p1.catch(function(e) { console.log(e); // "Uh-oh!" });
推薦使用catch方法,不要在then方法中定義rejected狀態的回調函數;這是因為使用catch還可以捕獲在then方法執行中存在的錯誤。
// bad promise.then(function(data) { // success }, function(err) { // error }); // good promise.then(function(data) { // success }) .catch(function(err) { // error })
3、Promise.prototype.finally()
finally() 方法返回一個Promise。在promise結束時,無論結果是fulfilled或者是rejected,都會執行指定的回調函數。這為在Promise是否成功完成后都需要執行的代碼提供了一種方式。這避免了同樣的語句需要在then()和catch()中各寫一次的情況。
參數:
回調函數,不接收任何參數
返回值:
返回一個新的Promise實例對象
let p1 = new Promise(function(resolve, reject) { throw 'Uh-oh!'; }); p1.catch(function(e) { console.log(e); // "Uh-oh!" }).finally(function() { console.log('這段代碼最終都會執行'); });
- promise封裝ajax請求
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <title>promise基本使用</title> </head> <body> <script> let promise = new Promise(function(resolve,reject){ // ajax發送異步請求 $.ajax({ // 請求路徑 url:'http://47.100.84.201:8888/carousel/findAll', // 成功回調 success(res){ console.log("成功回調",res); // 通過resolve將成功的回調傳遞出去 //resolve(res); }, // 失敗回調 error(err){ console.log("失敗回調",err); // 通過reject將失敗的回調傳遞出去 //reject(err); } }) }) // 通過promise實例對象的實例方法對數據進行操作 promise .then(res => console.log("接收到resolve傳遞過來的數據" + res)) .catch(err => console.log("接收reject傳遞的數據" + err)) .finally(()=>{ console.log("無論成功還是失敗都會調用!") }) </script> </body> </html>
分析:當在promise實例對象中ajax的兩個回調函數中使用
console.log("成功回調",res)
和console.log("失敗回調",err);
語句反映調用結果(成功或失敗)時,瀏覽器控制臺并不會執行thencatchfinally方法中的內容,因為此時then方法中并沒有接收到來自ajax的res,catch方法有沒有接收到來自ajax的err,所以并不會執行箭頭函數中的語句。
當改為resolve(res);
和reject(err);
時結果如下:
- promise層級調用
假設有三個文件first.txt,second.txt,third.txt,讀取文件
第一種方式:
使用普通方式進行層級讀取文件(不推薦),如下:
const fs = require("fs"); fs.readFile('../FILE/first.txt',(err,data1) => { fs.readFile('../FILE/second.txt',(err,data2)=>{ fs.readFile('../FILE/second.txt',(err,data3)=>{ let result = data1 + 'tn' + data2 + 'tn' + data3; console.log(result); //... //如果后面還有其他文件呢,會導致回調地獄,代碼會橫向變得很寬很長,并且這里data不能重名,需要不斷的取名字 }); }); });
第二種方式:
使用promise實現,解決縮進問題
const fs = require("fs"); // 初始化promise:讀取第一個文件,使用resolve函數傳遞出去讀取到的數據,用Promise對象接收 const promise = new Promise((resolve,reject)=>{ fs.readFile('../FILE/first.txt',(err,data)=>{ resolve(data); }) }) // 執行回調函數 promise.then(value => { //先看能不能獲取到value值 // console.log(value); //輸出的是buffer // console.log(value.toString()); //可以使用toString方法轉化buffer為正常字符串 // then方法的返回值是一個promise對象,所以這里直接使用return返回一個promise對象 return new Promise((resolve,reject)=>{ // promise中的主要操作也是讀取文件內容 fs.readFile('../FILE/second.txt',(err,data)=>{ // 將讀取到的數據傳遞出去,這里將讀取到的數據放到了數組中,一起傳了出去 // value是初始化時讀取文件first.txt的內容,data指的是當前讀到的文件內容 resolve([value,data]); }) }) //使用鏈式調用方式繼續調用,讀取下一個文件的內容 }).then(value=>{ return new Promise((resolve,reject)=>{ fs.readFile('../FILE/third.txt',(err,data)=>{ // 將讀取到的data通過push方法添加進數組中 // 這里的value是前面傳過來的數組 value.push(data); resolve(value); }) }) }).then(value=>{ // 輸出一一讀取文件后的結果 console.log(value.toString()); // 這是第一個文件,這是第二個文件,這是第三個文件 // 文件間通過逗號分隔 })
雖然目前使用promise的代碼量確實比較多,但卻可以避免代碼橫向增多的問題,不會影響代碼閱讀
靜態方法
定義在Promise中的方法,通過Promise可以直接調用。
1、Promise.all([p1,p2])
Promise.all用于將多個 Promise 實例,包裝成一個新的 Promise 實例
參數:
數組,數組中的元素為Promise實例
返回值:
Promise實例,當p1,p2狀態都為fulfilled時候,該實例的狀態才為fulfilled,此時p1,p2的返回值組成一個數組,傳遞給該實例的回調函數;只要p1,p2的返回值有一個變為rejected,該實例狀態為rejected。
const promise1 = Promise.resolve(3); //該方法用于將現有對象轉化為Promise實例 const promise2 = 42; const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); }); // expected output: Array [3, 42, "foo"]
2、Promise.race([p1,p2])
Promise.race用于將多個 Promise 實例,包裝成一個新的 Promise 實例
參數:
數組,數組中的元素為Promise實例
返回值:
Promise實例,當p1,p2之中有一個實例率先改變狀態,該實例的狀態就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給該實例的回調函數。(誰執行的快就返回誰)
const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, 'one'); }); const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'two'); }); Promise.race([promise1, promise2]).then((value) => { console.log(value); // Both resolve, but promise2 is faster }); // expected output: "two"
3、Promise.any([p1,p2])
用于將多個 Promise 實例,包裝成一個新的 Promise 實例
參數:
數組,數組中的元素為Promise實例
返回值:
Promise實例,只要p1,p2狀態有一個變為fulfilled,該實例的狀態為fulfilled;p1,p2狀態都變為rejected,該實例狀態才為rejected。
const pErr = new Promise((resolve, reject) => { reject("總是失敗"); }); const pSlow = new Promise((resolve, reject) => { setTimeout(resolve, 500, "最終完成"); }); const pFast = new Promise((resolve, reject) => { setTimeout(resolve, 100, "很快完成"); }); Promise.any([pErr, pSlow, pFast]).then((value) => { console.log(value); // pFast fulfils first }) // expected output: "很快完成"
4、Promise.resolve()
用于將現有對象轉化為Promise實例
參數:
任意值
const promise1 = Promise.resolve(123); promise1.then((value) => { console.log(value); // expected output: 123 });
5、Promise.reject()
返回一個新的 Promise 實例,該實例的狀態為rejected。
參數:
錯誤信息
Promise.reject(new Error('fail')).then(function() { // not called }, function(error) { console.log(error); // Stacktrace });
【