【JavaScript進階】深入理解JavaScript中ES6的Promise的作用並實現一個自己的Promise
阿新 • • 發佈:2018-11-09
1.Promise的基本使用
1 // 需求分析: 封裝一個方法用於讀取檔案路徑,返回檔案內容 2 3 const fs = require('fs'); 4 const path = require('path'); 5 6 7 /** 8 * 把一個回撥函式才分成兩個回撥函式 9 * @param filename 10 * @param successCallback 11 * @param errorCallback 12 */ 13 function getFileByPath(filename, successCallback, errorCallback) {14 fs.readFile(filename, 'utf-8', (err, data) => { 15 if (err) { 16 return errorCallback(err); 17 } 18 successCallback(data); 19 }); 20 } 21 22 23 let filename1 = path.join(__dirname, './files/1.txt'); 24 let filename2 = path.join(__dirname, './files/2.txt');25 let filename3 = path.join(__dirname, './files/3.txt'); 26 27 28 // 1. 普通的函式呼叫方式【回撥巢狀的問題???, 會產生一個回撥地獄】 29 getFileByPath(filename1, function (data) { 30 // 成功的回撥函式 31 console.log(data); 32 // 2. 如何實現檔案1 2 3 按順序讀取資料 33 getFileByPath(filename2, function (data) { 34 console.log(data);35 // 2. 如何實現檔案1 2 3 按順序讀取資料 36 getFileByPath(filename3, function (data) { 37 console.log(data); 38 }, function (err) { 39 console.log(err); 40 }) 41 }, function (err) { 42 console.log(err); 43 }) 44 }, function (err) { 45 // 失敗的回撥函式 46 console.log(err.message); 47 }) 48 49 50 51 52 // 2. 使用ES6中的Promise物件,來解決回撥地獄的問題 53 // 本質:本質是為了解決回撥地獄的問題,並不能幫我們減少程式碼量 54 // 轉換成為一個串聯的函式串
2. 形式上的和具體的Promise非同步執行操作的區別
為什麼要使用Promise???
使用ES6中的Promise物件,來解決回撥地獄的問題
// 本質:本質是為了解決回撥地獄的問題,並不能幫我們減少程式碼量 // 轉換成為一個串聯的函式串 // ========================================================================================== // Promise的基本使用及注意事項: // 2.1 Promise實際上是一個建構函式,我們是可以直接通過new Promise() 的方式來得到一個Promise的例項的 // 2.2 在Promise上面的 兩個函式, resolve 是一個成功之後的回撥函式, 和 reject是一個失敗的回撥函式 // 2.3 在 Promise建構函式的prototype 上面有一個.then 的方法, 只要是Promise 建構函式建立的 例項物件,都是可以訪問到.then這個方法的 // 2.4 Promise表示一個非同步操作,每當們建立一個新的Promise 例項, 就表示建立了一個非同步操作 // 2.5 非同步操作的狀態只能有兩種狀態 // 2.5.1 非同步執行成功: 會執行成功的回撥函式resolve,把成功的結果返回給呼叫者 // 2.5.2 非同步執行失敗: 會執行失敗的回撥函式reject, 把錯誤的結果返回給呼叫者 // 2.5.3 Promise的例項是一個非同步操作,因此我麼是無法直接通過 return來吧操作的結果返回給呼叫者的;就只能通過回撥函式的方式吧成功或者失敗的結果返回個呼叫者 // 2.6 我們可以在new 出來的Promise例項物件身上呼叫.then() 方法, 預先為這個Promise非同步操作, 預先指定成功(resolve)和失敗(rejecy)的回撥函式。 // 2.7 也就是.then(resolve, reject) 裡面傳遞的是一個實參物件, 然後我們到時候直接呼叫的實際上是一個resolve, reject的形參物件 // ==========================================================================================
// 需求分析: 封裝一個方法用於讀取檔案路徑,返回檔案內容 const fs = require('fs'); const path = require('path'); /** * 把一個回撥函式才分成兩個回撥函式 * @param filename * @param successCallback * @param errorCallback */ function getFileByPath(filename, successCallback, errorCallback) { fs.readFile(filename, 'utf-8', (err, data) => { if (err) { return errorCallback(err); } successCallback(data); }); } let filename1 = path.join(__dirname, './files/1.txt'); let filename2 = path.join(__dirname, './files/2.txt'); let filename3 = path.join(__dirname, './files/3.txt'); // 1. 普通的函式呼叫方式【回撥巢狀的問題???, 會產生一個回撥地獄】 getFileByPath(filename1, function (data) { // 成功的回撥函式 console.log(data); // 2. 如何實現檔案1 2 3 按順序讀取資料 getFileByPath(filename2, function (data) { console.log(data); // 2. 如何實現檔案1 2 3 按順序讀取資料 getFileByPath(filename3, function (data) { console.log(data); }, function (err) { console.log(err); }) }, function (err) { console.log(err); }) }, function (err) { // 失敗的回撥函式 console.log(err.message); }) // 建立一個promise 例項物件, 這裡的這個promise,只是代表一個【形式上】的非同步操作, 也就是我們只找到這是一個非同步操作, 還不清楚下一步需要執行的操作??? // var promise = new Promise(); // 這是一個具體的非同步操作, 通過使用function來實際上指定了一個具體的非同步操作 var promise = new Promise(function () { // 在這個裡面指定了一個具體的非同步操作 類似於fs.readFile()這個非同步操作 }); // 此時的promise就是一個非同步執行的結果, 每當new 一個Promise例項, 就會執行這個非同步操作中的程式碼 // 處理建立一個例項物件,還會立即呼叫這個Promise建構函式中傳遞的哪一個函式function, 執行function裡面的程式碼 var promise = new Promise(function () { fs.readFile(filename1, 'utf-8', function (err, data) { if (err) { throw err; } console.log('資料讀取成功:', data); }) }) // 為了實現按需讀取,可以把Promise放在一個函式中 /** * 使用Promise實現的一個數據讀取並返回 * @param filename */ function getFileByFilename(filename) { // JS 中的函式作用域: 內部的成員只要沒有return出去, 外部就是不能訪問到的 var promise = new Promise(function (resolve, reject) { fs.readFile(filename, 'utf-8', function (err, data) { if (err) { // throw err; return reject(err); } // console.log('資料' + filename + '中的資料讀取成功:', data); resolve('資料' + filename + '中的資料讀取成功:' + data) }) }) return promise; } // 獲取到一個promise var promise = getFileByFilename(filename3); console.log('我是一個promise例項物件', promise); // 通過.then 預先指定了一個執行成功/失敗的回撥函式 promise.then(function (data) { console.log(data, '資料獲取成功————————————————'); }, function (err) { console.log(err, '資料獲取失敗——————————————'); })
3. Promise執行的步驟分析
// 第0步:先在記憶體中定義了一個getFileByFilename方法 function getFileByFilename(filename) { // 第二步: 建立一個promise物件,並立即執行後面的 function函式 var promise = new Promise(function (resolve, reject) { console.log(resolve, reject); // 這個函式在建立Promise例項物件的時候就會立即執行,但是這裡是一個非同步執行,就會放到一個事件佇列中去,等待執行 // 第六步:.then執行完畢之後就會執行這部分,此時上面的resolve和reject已經被繫結為剛開始設定的兩個引數(形參和實參建立了聯絡)【非同步事件放在事件佇列中去】 fs.readFile(filename, 'utf-8', function (err, data) { console.log(resolve, reject); if (err) { // 第七步:函式執行完畢之後開始去訪問這個函式或者resolve函式(由於.then 函式是先執行的 ,因此此時的這個resolve 函式就指向了傳過來的兩個引數) return reject(err); } resolve('資料' + filename + '中的資料讀取成功:' + data) }) }) // console.log(resolve, reject); // 第三步: 直接返回建立的這個promise物件例項 return promise; } // 第一步: 呼叫getgetFileByFilename函式 / 第四步: 在這裡得到建立好的物件例項 var promise = getFileByFilename(filename3); // 第五步:通過.then 預先指定了一個執行成功/失敗的回撥函式(讀取檔案的操作是非同步的,這裡的操作相當於是一個給resolve和reject這兩個函式來繫結一個函式, 傳進去的是兩個實參物件) promise.then(function (data) { console.log(data, '資料獲取成功————————————————'); }, function (err) { console.log(err, '資料獲取失敗——————————————'); })
4. Promise捕獲異常的兩種方式
// 讀取檔案1 /** * 在上一個.then 中,返回一個新的 promise例項, 可以繼續使用下一個.then來處理(類似於JQuery中的鏈式訪問) * 【注意事項1】:如果前面的Promise執行失敗, 我們不想讓後續的Promise終止,可以為每一個promise指定失敗的回撥 * 【注意事項2】:如果前面的Promise執行失敗, 後面沒有繼續向下執行的意義了,一旦有錯誤,就立即終止所有的Promise(不在每一個裡面指定失敗的回撥函式) */ getFileByFilename(filename1) .then(function (data) { console.log(data); // 讀取檔案2 return getFileByFilename(filename2); }/*, function (err) { console.log('程式出錯:', err.message); // 失敗之後也要return 一個新的promise return getFileByFilename(filename2); }*/).then(function (data) { console.log(data); // 讀取檔案3 return getFileByFilename(filename3); }).then(function (data) { console.log(data); }).catch(function (err) { // catch 這個函式的錯誤的作用:只要前面有任何promise執行失敗,就會立即終止所有的promise的執行,然後就會進入到catch裡面的回撥函數了 console.log('我是一個用來捕獲程式錯誤的小東東……………………' + err.message); })
5.使用JQuery中的Promise傳送Ajax請求資料
$.ajax({ url : 'http://192.168.1.106:8081/api/getBroadcastPictures', type : 'GET', dataType : 'json', }).then(function (data) { // 前面返回的是一個promise物件 console.log(data); })
6. 實現一個自己封裝的Promise物件
"use strict"; // 實現一個簡單的Promise function Promise(executor) { // 第三步:這裡的executor 是一個執行器, 是使用者傳遞過來的一個引數資訊(初始化例項物件中的所有引數資訊) this.status = 'pending'; // 初始的狀態資訊 this.success = undefined; // 執行成供的值 this.error = undefined; // 執行失敗的值 var self = this; function resolve(data) { // 如果執行成功的話 if (self.status === 'pending') { // 重置狀態碼 self.status = 'resolved'; self.success = data; } } function reject(err) { // 如果執行失敗的話 if (self.status === 'pending') { self.status = 'rejected'; self.error = err; } } // 第四步:開始執行傳遞過來的兩個函式引數 executor(resolve, reject); } /** * 傳遞的是兩個引數,表示的是兩個函式(這是一個非同步執行的函式) * @param resolve * @param reject */ Promise.prototype.then = function (resolve, reject) { if (this.status === 'resolved') { // 執行成功 resolve(); } if (this.status === 'rejected') { // 執行失敗 reject(); } } // 測試用例 function getData() { // 第二步: 建立一個Promise的例項物件,裡面的函式是立即執行的 var promise = new Promise(function (resolve, reject) { // 這裡是一個非同步操作 setTimeout(function () { console.log('這裡是一個十分耗時的操作!'); // 如果執行成功的話 if(1 === 0) { console.log('執行成功!') resolve(true); } else { console.log('執行失敗!') reject(false); } }, 1000) }); return promise; } // 這是一個物件, 物件身上有一個then 方法 // 第一步:開始執行getData() 函式 getData().then(function (data) { console.log(data); }, function (err) { console.log(err); });