手摸手教你實現一個簡單的Promise

一隻前端萌新發表於2019-02-15

我們需要Promise嗎?

image

看下面這段程式碼

$.ajax(url1, function(data1){
    // do something...
    $.ajax(url2, function(data2){
        // do something...
        $.ajax(url3, function(data3){
            // do something...
            done(data3); // 返回資料
        })
    });
});
複製程式碼
  • 在沒有非同步物件時候很容易產生callback hell(回撥地獄)的問題 層層巢狀的程式碼極不方便維護 更不方便閱讀
  • 其實在Jquery時代就已經有了自己的非同步物件$.Deferred比如說我們常用的$.ajax他就是一個非同步物件 在使用非同步物件修改過後的程式碼

非同步並行請求 並在結果都返回後進行處理

$.when($.ajax({
    url: 'https://www.easy-mock.com/mock/5937664f91470c0ac1073a73/example/test'
}), $.ajax({
    url: 'https://www.easy-mock.com/mock/5937664f91470c0ac1073a73/example/test'
})).done((p1, p2) => {
    console.log(p1, p2)
})
複製程式碼
let b = $.Deferred()
function a(dtd) {
    setTimeout(function () {
        console.log(1234)
        dtd.resolve('b is done')
        
    }, 400)
    return dtd

}


$.when(a(b)).done(res => {
    console.log(res)
})

複製程式碼

看起來非同步物件使用起來還是很方便的它能幫助我們完成一些非同步程式碼採用同步寫法就可以完成事情.

Promise是什麼?

Promise實現規範 promise A+

  • 是ES6新增的一個特徵,它已被列入ES6的正式規範中
  • Promise物件可以理解為一次執行的非同步操作,使用promise物件之後可以使用一種鏈式呼叫的方式來組織程式碼;讓程式碼更加的直觀。也就是說,有了Promise物件,就可以將非同步操作以同步的操作的流程表達出來,避免了層層巢狀的回撥函式
  • 解決回撥地獄的問題
  • 便於維護
  • 解決同步非同步的返回結果 按照順序
  • 鏈式呼叫(JQ 實現鏈式呼叫依賴返回this) Promise不能返回this Promise實現鏈式呼叫依賴返回一個新的Promise
  • 如果then中無論成功還是失敗的回撥函式只要有返回值就會走下一個then中的成功 如果有錯誤就會走下一個then的失敗
  • 如果第一個promise 返回了一個普通值,會直接進入下一次then成功的回撥,如果第一個promise返回了一個promise需要等待返回的promise執行後的結果傳遞給下一個then
  • resolvePromise
  • 判斷x是否是promise ,如果x是物件並且x的then方法是函式我們就認為他是一個promise
  • promise 值穿透 空then直接穿過
  • Promise 規範中要求,所有onFufiled和onRejected都需要非同步執行
graph LR
pendding等待態-->Rejected拒絕態
pendding等待態-->Fulfilled執行態
複製程式碼

基本用法

跟$.Deffered很相似 都是依賴返回一個非同步物件來解決回撥地獄的問題

function test() {
    return new Promise((res, rej) => {
        setTimeout(function () {
            console.log('延遲執行')
            res('done')
        }, 2000)
    })
}
test().then(function (res) {
    console.log(res)
})

複製程式碼

實現一個簡單的Promise

從上面程式碼我們可以看出一個Promise有幾個特點

  • 引數為一個函式該函式包含兩個方法resolve跟reject
  • 返回的例項上包含then方法且then方法的呼叫時機與resolve和reject的執行時機相關
  • resolve和reject的返回引數直接傳入then方法中

初始化一個Promise

function Promise(executor) {
    //executor 執行器
    this.status = 'pending'
    this.reason = null
    this.data = null

    function resolve() {

    }
    function reject() {

    }
    executor(resolve, reject)
}
Promise.prototype.then = function (res, rej) {
    
}


複製程式碼

增加對呼叫resolve 以及reject時資料傳入的處理

function Promise(executor) {
    //executor 執行器
    this.status = 'pending'
    this.reason = null
    this.data = null
    const _this = this;
    function resolve(data) {
        if (_this.status == 'pending') {
            _this.data = data
            _this.status = 'onFulfilled'
        }
    }
    function reject(e) {
        if (_this.status == 'pending') {
            _this.reason = e
            _this.status = 'rejected'
        }
    }
    executor(resolve, reject)
}
Promise.prototype.then = function (res, rej) {

}
複製程式碼

執行時對Promise狀態進行修改以及then函式的呼叫時機進行處理

function Promise(executor) {
    //executor 執行器
    this.status = 'pending'
    this.reason = null
    this.data = null
    const _this = this;
    function resolve(data) {
        if (_this.status == 'pending') {
            _this.data = data
            _this.status = 'onFulfilled'
        }
    }
    function reject(e) {
        if (_this.status == 'pending') {
            _this.reason = e
            _this.status = 'rejected'
        }
    }
    executor(resolve, reject)
}
Promise.prototype.then = function (res, rej) {
    const _this =  this
    if(_this.status=='onFulfilled'){
        res(_this.data)
        return 
    }
    if(_this.status=='rejected'){
        res(_this.reason)
    }
}

複製程式碼

測試走一走

function test() {
    return new Promise(function (resolve, reject) {
        console.log('promise is running')
        resolve('2')
    })
}
test().then((res) => {
    console.log(res)
}, (e) => {
    console.log(e, e)
})
複製程式碼

image

OK同步程式碼通過

接下來實現非同步呼叫

  • 新增兩種屬性 onFulFilledList onRejectedList 分別在非同步執行時呼叫
function Promise(executor) {
    //executor 執行器
    this.status = 'pending'
    this.reason = null
    this.data = null
    this.onFulFilledList = []
    this.onRejectedList = []
    const _this = this;
    function resolve(data) {
        if (_this.status == 'pending') {
            _this.data = data
            _this.status = 'onFulfilled'
        }
    }
    function reject(e) {
        if (_this.status == 'pending') {
            _this.reason = e
            _this.status = 'rejected'
        }
    }
    executor(resolve, reject)
}
Promise.prototype.then = function (res, rej) {
    const _this = this
    if (_this.status == 'onFulfilled') {
        res(_this.data)
        return
    }
    if (_this.status == 'rejected') {
        rej(_this.reason)
    }
}

複製程式碼

對非同步呼叫的成功函式以及失敗函式進行佇列儲存 方便呼叫 以及呼叫時直接執行回撥佇列中方法

function Promise(executor) {
    //executor 執行器
    this.status = 'pending'
    this.reason = null
    this.data = null
    this.onFulFilledList = []
    this.onRejectedList = []
    const _this = this;
    function resolve(data) {
        if (_this.status == 'pending') {
            _this.data = data
            _this.status = 'onFulfilled'
            _this.onFulFilledList.forEach(element => {
                element(_this.data)
            });
        }
    }
    function reject(e) {
        if (_this.status == 'pending') {
            _this.reason = e
            _this.status = 'rejected'
            _this.onRejectedList.forEach(element => {
                element(_this.reason)
            });
        }
    }
    executor(resolve, reject)
}
Promise.prototype.then = function (res, rej) {
    const _this = this
    if (_this.status == 'onFulfilled') {
        res(_this.data)
        return
    }
    if (_this.status == 'rejected') {
        rej(_this.reason)
    }
    if (_this.status == "pending") {
        _this.onFulFilledList.push(res)
        _this.onRejectedList.push(rej)
    }
}
複製程式碼

測試程式碼


function test() {
    return new Promise((res, rej) => {
        setTimeout(function () {
            console.time('promise')
            console.log(123)
            res('4')
        }, 2000)
    })
}
test().then((
    params
) => {
    console.log(params)
    console.timeEnd('a')
})
複製程式碼

image

這就實現了最最簡單的Promise非同步呼叫了

上面的內容把ES6中的Promise的基本方法給大家講了一下,希望對大家有多幫助。

相關文章