搜尋
首頁web前端js教程實現Promise的詳細步驟

實現Promise的詳細步驟

Mar 29, 2019 am 09:13 AM
es6javascriptpromise

這篇文章帶給大家的內容是關於實現Promise的詳細步驟,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

1、constructor

首先我們都知道Promise 有三個狀態,為了方便我們把它定義成常數

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

接下來我們來定義一個類別

class MyPromise {
    constructor(executor) {
        //控制状态,使用了一次之后,接下来的都不被使用
        this.state = PENDING;
        this.value = null;
        this.reason = null;
        
        // 定义resolve函数
        const resolve = value => {
            if (this.state === PENDING) {
                this.value = value;
                this.state = FULFILLED;
            }
        }
        
        // 定义reject函数
        const reject = value => {
            if (this.state === PENDING) {
                this.reason = value;
                this.state = REJECTED;
            }
        }
        
        // executor方法可能会抛出异常,需要捕获
        try {
             // 将resolve和reject函数给使用者  
            executor(resolve, reject);
        } catch (error) {
            // 如果在函数中抛出异常则将它注入reject中
            reject(error);
        }
    }
}

到這基本上比較好理解我簡單說明一下

executor:這是實例Promise物件時在建構器中傳入的參數,一般是一個function(resolve,reject){}

state:`Promise的狀態,一開始是預設的pendding狀態,每當呼叫道resolve和reject方法時,就會改變其值,在後面的then`方法中會用到

# value:resolve回呼成功後,呼叫resolve方法裡面的參數值

reason:reject回呼成功後,呼叫reject方法裡面的參數值

resolve:宣告resolve方法在建構器內,透過傳入的executor方法傳入其中,用以給使用者回呼

reject:宣告reject方法在建構器內,透過傳入的executor方法傳入其中,用以給使用者回呼

2、then

then就是將Promise中的resolve或是reject的結果拿到,那麼我們就能知道這裡的then方法需要兩個參數,成功回調和失敗回呼,上程式碼!

then(onFulfilled, onRejected) {
    if (this.state === FULFILLED) {
        onFulfilled(this.value)
    }
    if (this.state === REJECTED) {
        onRejected(this.reason)
    }
}

我們來簡單的運行一下測試程式碼

const mp = new MyPromise((resolve, reject)=> {
    resolve('******* i love you *******');
})
mp.then((suc)=> {
console.log(11111, suc);
}, (err)=> {
console.log('****** 你不爱我了*******', err)
})

// 11111 '******* i love you *******'

這樣看著好像沒有問題,那我們來試試非同步函數呢?

const mp = new MyPromise((resolve, reject)=> {
    setTimeout(()=> {
        resolve('******* i love you *******');
    }, 0)
})
mp.then((suc)=> {
console.log(11111, suc);
}, (err)=> {
console.log('****** 你不爱我了*******', err)
})

我們會發現什麼都沒有列印,哪裡出問題了呢?原來是因為非同步的原因,當我們執行到then的時候this. state的值還沒發生改變,所以then裡面的判斷就失效了。那我們該怎麼解決呢?

這就要說回經典得callback 了。來上原始碼

// 存放成功回调的函数
this.onFulfilledCallbacks = [];
// 存放失败回调的函数
this.onRejectedCallbacks = [];

const resolve = value => {
    if (this.state === PENDING) {
        this.value = value;
        this.state = FULFILLED;
        this.onFulfilledCallbacks.map(fn => fn());
    }
}

const reject = value => {
    if (this.state === PENDING) {
        this.value = value;
        this.reason = REJECTED;
        this.onRejectedCallbacks.map(fn => fn());
    }
}

在then裡面加上

then(onFulfilled, onRejected) {
    // ... 
    if(this.state === PENDING) {
        this.onFulfilledCallbacks.push(()=> {
            onFulfilled(this.value);
        });
        this.onRejectedCallbacks.push(()=> {
            onRejected(this.value);
        })
    }
}

好了,到這異步的問題解決了,我們再來執行剛才的測試程式碼。結果就出來了。到這我們還缺什麼呢?

鍊式呼叫

當我們不傳參數時要當什麼運行

這二個的思路也都很簡單,鍊式呼叫也就是說我們再回傳一個promise的實例就好了。而不傳參則就是預設值的問題了。下面來看原始碼

then(onFulfilled, onRejected) {
    let self = this;
    let promise2 = null;
    //解决onFulfilled,onRejected没有传值的问题
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y
    //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后then的resolve中捕获
    onRejected = typeof onRejected === 'function' ? onRejected : err => {
        throw err;
    }

    promise2 = new MyPromise((resolve, reject) => {
        if (self.state === PENDING) {
            console.log('then PENDING')
            self.onFulfilledCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(self.value);
                    console.log(333333, x, typeof x);
                        self.resolvePromise(promise2, x, resolve, reject);
                    } catch (reason) {
                        reject(reason);
                    }
                }, 0)

            });
            self.onRejectedCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        let x = onRejected(self.reason);
                        self.resolvePromise(promise2, x, resolve, reject);
                    } catch (reason) {
                        reject(reason);
                    }
                }, 0);
            });
        }

        if (self.state === FULFILLED) {
            console.log('then FULFILLED')
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value);
                    self.resolvePromise(promise2, x, resolve, reject);
                } catch (reason) {
                    reject(reason);
                }
            }, 0);
        }

        if (self.state === REJECTED) {
            console.log('then REJECTED')
            setTimeout(() => {
                try {
                    let x = onRejected(self.reason);
                    self.resolvePromise(promise2, x, resolve, reject);
                } catch (reason) {
                    reject(reason);
                }
            })
        }
    });

    return promise2;
}

為什麼外面要包一層setTimeout? :因為Promise本身就是一個非同步方法,屬於微任務一列,必須得在執行棧執行完了在去取他的值,所以所有的回傳值都得包一層非同步setTimeout。

resolvePromise是什麼? :這其實是官方Promise/A 的需求。因為你的then可以返回任何職,當然包括Promise對象,而如果是Promise對象,我們就需要將他拆解,直到它不是一個Promise對象,取其中的值。

3、resolvePromise

我們直接看程式碼

resolvePromise(promise2, x, resolve, reject) {
    let self = this;
    let called = false; // called 防止多次调用
    //因为promise2是上一个promise.then后的返回结果,所以如果相同,会导致下面的.then会是同一个promise2,一直都是,没有尽头
    //相当于promise.then之后return了自己,因为then会等待return后的promise,导致自己等待自己,一直处于等待
    if (promise2 === x) {
        return reject(new TypeError('循环引用'));
    }
    //如果x不是null,是对象或者方法
    if (x !== null && (Object.prototype.toString.call(x) === '[object Object]' || Object.prototype.toString.call(x) === '[object Function]')) {
        // x是对象或者函数
        try {
            let then = x.then;

            if (typeof then === 'function') {
                then.call(x, (y) => {
                    // 别人的Promise的then方法可能设置了getter等,使用called防止多次调用then方法
                    if (called) return;
                    called = true;
                    // 成功值y有可能还是promise或者是具有then方法等,再次resolvePromise,直到成功值为基本类型或者非thenable
                    self.resolvePromise(promise2, y, resolve, reject);
                }, (reason) => {
                    if (called) return;
                    called = true;
                    reject(reason);
                });
            } else {
                if (called) return;
                called = true;
                resolve(x);
            }
        } catch (reason) {
            if (called) return;
            called = true;
            reject(reason);
        }
    } else {
        // x是普通值,直接resolve
        resolve(x);
    }
}

為什麼要在一開始判斷promise2和x? :首先在Promise/A 中寫了需要判斷這兩者如果相等,需要拋出異常,我就來解釋一下為什麼,如果這兩者相等,我們可以看下下面的例子,第一次p2是p1. then出來的結果是個Promise對象,這個Promise物件在被創建的時候呼叫了resolvePromise(promise2,x,resolve,reject)函數,又因為x等於其本身,是個Promise,就需要then方法遞歸它,直到他不是Promise對象,但x(p2)的結果還在等待,他卻想執行自己的then方法,就會導致等待。

為什麼要遞歸去呼叫resolvePromise函數? :相信細心的人已經發現了,我在這裡使用了遞歸調用法,首先這是Promise/A 中要求的,其次是業務場景的需求,當我們碰到那種Promise的resolve裡的Promise的resolve裡又包了一個Promise的話,就需要遞歸值,直到x不是Promise物件。

4、catch

//catch方法
catch(onRejected){
  return this.then(null,onRejected)
}

5、finally

finally方法用來指定不管 Promise 物件最後狀態如何,都會執行的操作。此方法是 ES2018 引入標準的。

finally(fn) {
    return this.then(value => {
        fn();
        return value;
    }, reason => {
        fn();
        throw reason;
    });
};

6、resolve/reject

大家一定都看過Promise.resolve()、Promise.reject()這兩種用法,它們的作用其實就是回傳一個Promise對象,我們來實現一下。

static resolve(val) {
    return new MyPromise((resolve, reject) => {
        resolve(val)
    })
}
//reject方法
static reject(val) {
    return new MyPromise((resolve, reject) => {
        reject(val)
    })
}

7、all

all方法可以說是Promise中很常用的方法了,它的作用就是將一個陣列的Promise物件放在其中,當全部resolve的時候就會執行then方法,當有一個reject的時候就會執行catch,並且他們的結果也是按著數組中的順序來排放的,那麼我們來實現一下。

static all(promiseArr) {
    return new MyPromise((resolve, reject) => {
        let result = [];

        promiseArr.forEach((promise, index) => {
            promise.then((value) => {
                result[index] = value;

                if (result.length === promiseArr.length) {
                    resolve(result);
                }
            }, reject);
        });
    });
}

8、race

race方法虽然不常用,但是在Promise方法中也是一个能用得上的方法,它的作用是将一个Promise数组放入race中,哪个先执行完,race就直接执行完,并从then中取值。我们来实现一下吧。

static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
        promiseArr.forEach(promise => {
            promise.then((value) => {
                resolve(value);
            }, reject);
        });
    });
}

9、deferred

static deferred() {
    let dfd = {};
    dfd.promies = new MyPromise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.rfeject = reject;
    });
    return dfd;
};

什么作用呢?看下面代码你就知道了

let fs = require('fs')
let MyPromise = require('./MyPromise')
//Promise上的语法糖,为了防止嵌套,方便调用
//坏处 错误处理不方便
function read(){
  let defer = MyPromise.defer()
  fs.readFile('./1.txt','utf8',(err,data)=>{
    if(err)defer.reject(err)
    defer.resolve(data)
  })
  return defer.Promise
}

10、测试

const mp1 = MyPromise.resolve(1);
const mp2 = MyPromise.resolve(2);
const mp3 = MyPromise.resolve(3);
const mp4 = MyPromise.reject(4);

MyPromise.all([mp1, mp2, mp3]).then(x => {
    console.log(x);
}, (err) => {
    console.log('err1', err);
})
MyPromise.race([mp1, mp4, mp2, mp3]).then(x => {
    console.log(x);
}, (err) => {
    console.log('err2', err);
})

var mp = new MyPromise((resolve, reject) => {
    console.log(11111);
    setTimeout(() => {
        resolve(22222);
        console.log(3333);
    }, 1000);
});
mp.then(x => {
    console.log(x);
}, (err) => {
    console.log('err2', err);
})
//11111
//[ 1, 2, 3 ]
//1
//3333
//22222

完整源码请查看

本篇文章到这里就已经全部结束了,更多其他精彩内容可以关注PHP中文网的的JavaScript教程视频栏目!!!

以上是實現Promise的詳細步驟的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:segmentfault。如有侵權,請聯絡admin@php.cn刪除
超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python還是JavaScript更好?Python還是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

如何安裝JavaScript?如何安裝JavaScript?Apr 05, 2025 am 12:16 AM

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器