1. 程式人生 > >【JavaScript進階】深入理解JavaScript中ES6的Promise的作用並實現一個自己的Promise

【JavaScript進階】深入理解JavaScript中ES6的Promise的作用並實現一個自己的Promise

 

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);
});