手寫 Promsie 原理

Promise的好處

Promsie 可以解決的問題

  1. 把你從回調地獄中解救出來
  2. 讓你優雅的捕獲錯誤
  3. 爲你分擔異步併發的難題
// 此處使用node舉例,不會不要緊,先混個臉熟。再見就不陌生了呀
let fs = require('fs');
// 異步讀取文件
fs.readFile('./name', function (err, data){
    if(err){}
    fs.readFile(data, function (err, address){
        if(err){}
        fs.readFile(address, function (err, product){
            // 1)深陷在回調地域中不能抽身
            if(err){
                // 2)捕獲錯誤。OMG,我哪裏錯了?!?!告訴我,肯定改~
            }
        });
    });
});

// 3) 你的名字、你的地址。都告訴我,驚喜纔會送到你面前呀~
fs.readFile('./name', function (err, data){});
fs.readFile('./address', function (err, data){});

Promise 使用的例子

狀態變化

  • Promise 有3種狀態
  • pending 等待態
  • fulfilled 成功態
  • rejected 失敗態

【等待態 -> 成功態】 or 【等待態 -> 失敗態】二選一,你來定。

圖片描述

  • Promsie 是個類,接收一個函數參數 executor 執行器,一上來就執行了。這裏是同步的。
  • 每一個Promsie的實例上都有一個then方法。是基於回調實現的。
console.log('一封情書');

let p = new Promise((resolve, reject)=>{
    console.log('executor請說出你的選擇:');
    resolve('你中意我~(*^▽^*)');
    reject('你發了好人卡(╥﹏╥)o');
});

p.then((value)=>{
    console.log('成功態', value);
}, (reason) => {
    console.log('失敗態', reason);
});

console.log('紙短情長');

圖片描述

鏈式調用

console.log('-----一封情書-----');

let p = new Promise((resolve, reject) => {
    console.log('executor 請說出你的選擇:');
    resolve('你中意我~(*^▽^*)');
    reject('你發了好人卡(╥﹏╥)o');
});

p.then((value) => {
    console.log('成功態---', value);
}, (reason) => {
    console.log('失敗態---', reason);
}).then((value) => {
    console.log('---愛你一萬年~(*^▽^*)');
}, (reason) => { 
    console.log('---傷心總是難免的o(╥﹏╥)o');
});
console.log('~~~紙短情長~~~');

圖片描述

異步請求

  • name.txt
zhaoxiajingjing
  • 3.js
let fs = require('fs');
let p = new Promise((resolve, reject)=>{
    fs.readFile('./name.txt', 'utf8', function (err, data){
        if(err){
            return reject(err);
        }
        resolve(data);
    });
});
p.then((value)=>{
    console.log('成功了', value);
}, (reason)=>{
    console.log('失敗了', reason);
});

圖片描述

複雜的使用 Promise

下面的例子輸出什麼呢?

let fs = require('fs');

function read(filePath) {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, 'utf8',function(err, data){
            if(err) {
                return reject(err);
            }
            resolve(data);
        });
    });
}

read('./name.txt')
// then-1
.then(function (data) {
    console.log('data①', data);
    return new Promise((resolve, reject) => {
        reject('錯誤了');
    });
})
// then-2
.then((data) => {
    console.log('data②', data);
}, err => {
    console.log('err②', err);
})
// then-3
.then((data) => {
    console.log('data③', data);
}, (err) => {
    console.log('err③', err);
});

圖片描述

OK,提煉一下重點:

  1. 有三個狀態。怎麼變化的?
  2. executor。怎麼執行的?
  3. then方法,成功態和失敗態的回調。
  4. then方法的鏈式調用。
  5. Promise 處理異步。

實現以上內容

Promise 基本實現

  • new Promise 會傳一個函數作爲參數。這個函數有兩個參數:resolve 成功 reject 失敗,都是用於改變狀態的。都在實例上,一個 Promise 一生只改變一次狀態
  • 每個實例上都有一個 then 方法,異步的。對於成功態和失敗態的回調:onFulfilled onRejected

代碼1,傳送門~

const PENDING = 'pending';
const SUCCESS = 'fulfilled';
const FAIL = 'rejected';

class Promise{
    constructor(execuotr){
        const status = PENDING; // 我等着你給答案~
        this.value;
        this.reason;

        let resolve = (value)=>{
            if(status === PENDING) {
                this.status = SUCCESS; // 你中意我~
                this.value = value;
            }
        };

        let reject = (reason)=>{
            if(status === PENDING) {
                this.status = FAIL; // 你發了好人卡
                this.reason = reason;
            }
        };

        // 是同步的哦~
        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }
    then(onFulfilled, onRejected){
        if(this.status === SUCCESS) {
            onFulfilled(this.value);  // 愛你一萬年~
        }
        if(this.status === FAIL) {
            onRejected(this.reason); // 傷心總是難免的
        }
    }
}

Pomise 解決異步問題

Promise 是個容器,裏面可以放一些異步的請求,請求成功了走成功態,請求失敗了走失敗態。當然,你要反過來走也可以噠~

代碼2,傳送門~

const PENDING = 'pending';
const SUCCESS = 'fulfilled';
const FAIL = 'rejected';

class Promise {
    constructor(executor){
        this.status = PENDING;
        this.value;
        this.reason;

        // 用來存儲 訂閱的內容的
        this.onSuccessCallbacks = [];
        this.onFailCallbacks = [];

        let resolve = (value)=>{
            if(this.status === PENDING) {
                this.status = SUCCESS;
                this.value = value;
                this.onSuccessCallbacks.forEach(fn => fn());
            }
        };
        let reject = (reason)=>{
            if(this.status === PENDING) {
                this.status = FAIL;
                this.reason = reason;
                this.onFailCallbacks.forEach(fn => fn());
            }
        };

        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }
    then(onFulfilled, onRejected){
        if(this.status === SUCCESS){
            onFulfilled(this.value);
        }
        if(this.status === FAIL){
            onRejected(this.reason);
        }
        // 當Promise裏面有異步請求控制狀態改變時,會先走到then方法裏面
        if(this.status === PENDING) {
            this.onSuccessCallbacks.push(()=>{
                onFulfilled(this.value);
            });
            this.onFailCallbacks.push(()=>{
                onRejected(this.reason);
            });
        }
    }
}

Promise 裏面有異步請求時候,會先走到 then方法裏面了。此時,需要把成功態回調和失敗態回調先存儲起來,等到異步請求回來以後變更了狀態,再觸發執行。

Promise 的鏈式

then 方法 返回一個新的 Promise

Promise 一生只能改變一次狀態。那麼,Promise 的鏈式調用then方法,說明每次都會返回一個新的Promise

代碼3,傳送門~

const SUCCESS = 'fulfilled';
const FAIL = 'rejected';
const PENDING = 'pending';

class Promise {
    constructor(executor) {
       // ... executor 代碼
    }
    then(onFulfilled, onRejected) {
        let promise2;

        promise2 = new Promise((resolve, reject)=>{
            if (this.status === SUCCESS) {
                try { // 用 try catch 捕獲同步的報錯
                    // 成功態的回調的返回值 x
                    // 【問題】 如果 x 是一個promsie,那麼需要取得x執行後的結果
                    let x = onFulfilled(this.value);
                    resolve(x);

                } catch (e) {
                    reject(e);
                }
            }
            if (this.status === FAIL) {
                try {
                    let x = onRejected(this.reason);
                    resolve(x);
                } catch (e) {
                    reject(e);
                }
            }
            if (this.status === PENDING) {
                this.onSuccessCallbacks.push(()=>{
                    try {
                        let x = onFulfilled(this.value);
                        resolve(x);
                    } catch (e) {
                        reject(e);
                    }
                });
                this.onFailCallbacks.push(()=>{
                    try {
                        let x = onRejected(this.reason);
                        resolve(x);
                    } catch (e) {
                        reject(e);
                    }
                });
            }
        });

        return promise2;
    }
}

解析 x

如何判斷 x 是Promsie,還是一個普通值?【參考規範 https://promisesaplus.com "Promsie A+" 2.2.7】

代碼4,傳送門~

const SUCCESS = 'fulfilled';
const FAIL = 'rejected';
const PENDING = 'pending';

function resolvePromise(promise2, x, resolve, reject) {
    // 死循環了,容錯
    if(promise2 === x) {
        return reject('TypeError: Chaining cycle detected for promise~~~~');
    }
    // 判斷 x 類型
    if(typeof x === 'function' || (typeof x === 'object' && x != null)) {
        // 這個纔有可能是 promsie
        try {
            let then = x.then;
            if(typeof then === 'function') {
                // 此時,認爲就是個promise
                // 如果promsie是成功的,那麼結果向下傳遞,如果失敗了就傳到下一個失敗裏面去
                then.call(x, y=>{
                    resolvePromise(promise2, y, resolve, reject);
                }, r => {
                    reject(r);
                });
            } else {
                resolve(x);
            }
        } catch (e) {
            reject(e);
        }
    } else {
        // x 肯定不是一個promsie
        resolve(x);
    }
}

class Promise {
    constructor(executor) {
        // ... executor 代碼
    }
    then(onFulfilled, onRejected) {
        let promise2;

        promise2 = new Promise((resolve, reject) => {
            if (this.status === SUCCESS) {
                // 用定時器模擬此時promise2已經能獲取到了
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            }
            // 其他情況同理,先以一個爲例說明
        });
        
        return promise2;
    }
}

嚴謹度

Promsie 給你的承諾,一句情話:一個 Promise 一生只改變一次狀態

代碼5,傳送門~

const SUCCESS = 'fulfilled';
const FAIL = 'rejected';
const PENDING = 'pending';

function resolvePromise(promise2, x, resolve, reject) {
    // 死循環了,容錯
    if(promise2 === x) {
        return reject('TypeError: Chaining cycle detected for promise~~~~');
    }
    let called;
    if(typeof x === 'function' || (typeof x === 'object' && x != null)) {
        try {
            let then = x.then;
            if(typeof then === 'function') {
                then.call(x, y=>{
                    if(called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, r => {
                    if(called) return;
                    called = true;
                    reject(r);
                });
            } else {
                resolve(x);
            }
        } catch (e) {
            if(called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

class Promise {
    constructor(executor) {
        // .... executor 的代碼
    }
    then(onFulfilled, onRejected) {
        let promise2;

        promise2 = new Promise((resolve, reject) => {
            if (this.status === SUCCESS) {
                // 用定時器模擬此時promise2已經能獲取到了
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            }
            // 其他情況同理,先以一個爲例說明
        });

        return promsie2;
    }
}

值的穿透

let p = new Promise((resolve, reject)=>{
    resolve(1000);
});
p.then().then().then(data => {
    console.log(data);
});

值可以傳過去

const SUCCESS = 'fulfilled';
const FAIL = 'rejected';
const PENDING = 'pending';

function resolvePromise(promise2, x, resolve, reject) {
    // ... 判斷 x 的值
}

class Promise {
    constructor(executor) {
        // ... executor 代碼
    }
    then(onFulfilled, onRejected) {
        // 值穿透
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
        onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
        // ... promsie2 的判斷
    }
}

測試

  • 測試這個庫是否符合我們的promise A+ 規範
  • promises-aplus-tests 用來測試當前的庫是否符合規範
  • npm i promises-aplus-tests -g
  • promises-aplus-tests 文件名

可測試代碼,傳送門~

Promise A+ 規範

Promsie 是一個構造函數,是個類。默認高版本瀏覽器,node都自帶了。不用考慮兼容性,放心大膽的使用吧!如果真不兼容,那就用es6-promsie包自己是一套吧~

Promsie A+

https://promisesaplus.com

容錯

上面的內容,還需要一部分容錯。就是當executor 裏面的有一個promsie的時候,執行的結果。

let Promise = require('./promise.js');
let p = new Promise((resolve, reject)=>{
    resolve(new Promise((resolve, reject)=>{
        reject(404);
    }));
});
p.then(value => console.log(1, value), reason => console.log(2, reason));

// 輸出:
// 2 404

在同步執行時,resolve 的value是一個Promsie,那麼需要等它的結果。

const SUCCESS = 'fulfilled';
const FAIL = 'rejected';
const PENDING = 'pending';

function resolvePromise(promise2, x, resolve, reject) {
    // ... 校驗x
}

class Promise {
    constructor(executor) {
        // ... code

        let resolve = (value) => {
            if (value instanceof Promise) {
                return value.then(resolve, reject);
            }

            if (this.status === PENDING) {
                this.status = SUCCESS;
                this.value = value;
                this.onSuccessCallbacks.forEach(fn => fn());
            }
        };
        // ... code
    }
    then(onFulfilled, onRejected) {
        // ... then 方法
    }
}

module.exports = Promise;

交流

朝霞的世界
在這裏,一起成長~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章