最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • promise A+规范及源码编写

    正文概述 掘金(伸手挽明月)   2020-12-19   297

    一、为什么需要promise?

    javascript是一门单线程语言,所以早期解决js异步问题时,一般都采用传入callback回调函数的方式来解决。
    例如 ajax请求就是最常见的js异步操作。我门需要在异步执行完成后,利用请求响应来进行下一步操作。如下利用setTimeout延时来模拟ajax异步请求,也就是请求1000毫秒完成以后,执行传入的callback回调函数并传入结果,完成异步处理。

    function dynamicFunc(cb) {
        setTimeout(function() {
        	var response = 'hello' 
        	cb(response)
        }, 1000)
    }
    
    dynamicFunc(function(res) {
        console.log(res) // 'hello'
    })
    

    上面的写法看似没有问题,但是如果在回调函数中继续处理异步,继续传入回调,如果嵌套过多,就会导致我们的代码不是越写越长,而是越写越宽,这就会在js书写过程中的形成Callback Hell(回调地狱)。因此es新特性增加了promise规范及相应的Promise构造类的实现,基本解决了代码Callback Hell问题。

    二、Promise构造函数的基础

    以下是一段最简单的Promise使用代码:

    function promise1() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                var response = 'hello' 
                resolve(response)
            }, 1000)
        })
    }
    
    function promise2() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                var response = 'hello2' 
                resolve(response)
            }, 2000)
        })
    }
    // 调用promise
    promise1().then(function(res) {
        console.log(res) // 'hello'
        promise2()
    }, function() {
    	console.log('reject error')
    })
    // 或者简写
    promise1().then(promise2)
    

    从上面实现代码可以看出,通过new关键字创建一个promise实例对象,同时参数需要传递一个回调函数,这个回调函数同时又接收两个参数resolve和reject。当回调函数中的(异步或者同步)代码执行完后,可以通过resolve修改promise「已完成」状态,或者通过reject修改promise「已拒绝」状态。状态一旦修改,将再也无法改变。创建的promise实例,可以通过.then()方法,来处理promise状态改变(异步代码执行完)后的执行和接收状态传递的参数。then() 方法需要传入一个函数参数,这个函数就是promise状态改变后执行的函数,而这个函数接收的参数就是resolve传递的参数。

    .then()方法还可以接收第二个参数,也是一个函数,这个函数可以捕获处理reject的拒绝状态,接收的参数也是reject执行传入的参数。当然除了利用.then()方法的第二个参数捕获已拒绝状态以外,还可以2种方式,一种是promise的.catch()方法,另一种是tryCatch异常处理。具体三种实现捕获异常的代码如下:

    function promise() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                var flag = false
                if(flag) {
                	resolve('yes')
                } else {
                	reject('error')
                }
            }, 1000)
        })
    }
    
    // 1. .then() 的第二个参数捕获错误  注意:resolve不需要的话,第一个参数可以传null
    promise().then(null, function(e) {
        console.log(e) // 'error'
    })
    
    // 2. .catch()
    promise().catch(function(e) {
        console.log(e) // 'error'
    }).then(null)
    
    // 3. try catch 注意:这种方式不推荐  有些浏览器或者node环境,无法捕获到
    try {
        promise().then(null)
    } catch(e) {
        console.log(e) // 'error'
    }
    
    • promise有三种状态 进行中、已完成、已拒绝。进⾏中状态可以更改为已完成或已拒绝,已经更改过状态后⽆法继续更改(例如从已完成改为已拒绝)。
    • es6中的Promise构造函数,我们构造之后,需要传入一个回调函数,函数接收两个参数,resolve和reject。resolve会修改当前promise为「已完成」状态,reject会修改当前promise为「已拒绝」状态。
    • 对于已完成的promise,可以通过.then()的第一个参数方法进行捕获处理,即可在上一个promise达到已完成时,继续执行下一个函数或 promise。同时可以利用resolve继续往下传递参数。
    • 对于已拒绝的promise,可以通过.then() 的第二个参数捕获,也可以.catch()方法捕获,还可以try catch进⾏捕获。

    三、(案例)利用Promise封装简单ajax

    function ajaxAsync(method, url, data) {
        return new Promise(function(resolve, reject) {
            var xhr = new XMLHttpRequest()
            xhr.open(method || 'GET', url, true)
            xhr.onreadystatechange = function() {
                if(this.readyState !== 4) {
                    return
                }
                if(this.status === 200) {
                    resolve(this.response)
                } else {
                    reject(new Error(this.statusText))
                }
            }
            xhr.send(data || null)
        })
    }
    
    // 使用 (可以先用catch捕获)
    ajaxAsync('GET', 'https://...', null)
        .catch(function(err) {
            throw err
        })
        .then(function(response) {
            console.log(response) // ajax响应结果
        })
    

    四、promise A+规范解读

    任何符合promise规范的对象或函数都可以称之为promise,promise A plus规范地址:promisesaplus.com/。
    下面我们从规范层面明白promise的使用细节。

    • promise: promise是一个拥有then方法的对象或函数,其行为符合本规范。
    • thenable:定义了then方法的对象或函数
    • value:值。指任何javascript的合法值(包括 undefined,thenable,promise)。
    • exception:异常。是使用throw语句抛出的一个值(value)。
    • reason:原因。表示一个promise的拒绝原因的值(value)。

    1、promise的状态

    promise在执行过程中,有且只有三种状态,进行中(Pending)、已完成(Fulfilled)、和已拒绝(Rejected)。 处于Pending状态时,可以变为Fulfilled或者Rejected。 处于Fulfilled状态时,不可变为其他任何状态,且必须拥有一个不可变的值(value)。 处于Rejected撞状态时,不可变为其他任何状态,且必须有一个不可变的原因(reason)。

    2、必须有一个then方法

    一个promise必须提供一个then方法去访问其当前的value或者reason。
    一个then方法接受两个参数:promise.then(onFulfilled, onRejected),两个参数都是可选参数,且都是函数。如果onFulfilled或 onRejected传入的不是函数,则忽略它们(例如 传null就是不进行任何处理)。

    如果onFulfilled是一个函数:
    • 当promise改变为已完成状态后该函数必须被调用,其第一个参数就是promise完成(resolve)的结果(值)。
    • promise执行完成前不可被调用
    • onFulfilled函数最多被调用一次
    如果onRejected是一个函数:
    • 当promise改变为已拒绝状态后该函数必须被调用,其第一个参数就是promise拒绝(reject)的结果(原因)。
    • promise拒绝执行前不可被调用
    • onRejected函数最多被调用一次
    在执行上下文堆栈仅包含平台代码之前,不得调用onFulfilled 或 onRejected。
    onFulfilled和onRejected必须作为普通函数调用(即非实例化调用,这样非严格模式下,函数内部的this指向window)。
    then方法可以实现链式调用
    • 当promise完成执行后,所有的onFulfilled需按照注册的顺序依次回调。
    • 当promise拒绝执行后,所有的onRejected需按照注册的顺序依次回调。
    then方法必须返回一个promise对象(这也是上述可以链式调用的原因),promise2 = promise.then(onFulfilled, onRejected)。
    • 只要onFulfilled或者onRejected返回的是一个值x,那么返回的promise2都会进入Fulfilled状态。
    • 如果onFulfilled或者onRejected抛出一个异常e,则promise2必须拒绝执行,并返回异常e。
    • 如果onFulfilled不是函数且promise的状态变成已完成,那么返回的promise2必须进入已完成状态且返回promise返回的值。
    • 如果onRejected不是函数且promise的状态变成已拒绝,那么返回的promise2必须进入已拒绝状态且返回promise返回的原因。

    例如:

    var promise = new Promise(function (resolve, reject) {
        reject()
    })
    var promise2 = promise.then(null, function () {
        return 123
    })
    
    promise3 = promise2
    .then(null, null)
    .then(false, false)
    
    promise3
    .then(function (val) {
        console.log(val)
        console.log('promise3...fulfilled')
    }, function (e) {
        console.log(e.message)
        console.log('promise3...rejected')
    })
    

    以上虽然promise是拒绝状态,但是在第一次onRejected捕获处理之后 返回的是值123,那么返回的promise2就是已完成状态的且返回的值就是123。然后已完成的promise2连续链式调用2次then,但是由于onFulfilled和onRejected都是null或者false,所以返回的promise3与promise2具有相同的完成状态和值123。所以最终打印结果是promise3...fulfilled。
    那么假如第一次onRejected捕获处理之后返回的不是123,而是直接throw一个错误。那么最终结果一定就是promise3...rejected。

    3、promise的解决过程

    promise的解决过程是一个抽象的操作,我们需要修改promise的状态,并传入值x。尤其是promise执行then()方法后,需要返回一个新的promise2,新的promise2的状态完全由then()方法传入的onFulfilled或onRejected函数执行的返回值x决定。当然如果onFulfilled或onRejected不是函数,则会将promise的状态和值透传给返回给promise2。

    如果x有then方法且看上去像一个promise,解决程序尝试使返回的promise接受x的状态,否则用x的值来执行返回的promise。

    • 如果x和返回的promise是同一个引用对象,则解决程序已TypeError的错误为拒绝原因来拒绝返回的promise。
    • 如果x为新的promise实例对象,则解决程序会一直等待这个promise状态修改为已完成或已解决,并且返回的promise最终接受这个promise的状态和值。
    • 如果x为object或function时(不常见),则首先尝试获取x.then, 如果抛出了一个异常错误e,则以抛出的错误为拒因来拒绝返回的promise。如果没有异常,则判断获取的x.then是不是一个函数方法,如果不是,则以普通值x为参数并修改返回的promise2为已完成状态;如果是一个函数,则将把x作为函数作用域的this来调用这个函数,并传入resolvePromise和rejectPromise两个参数(本质上就是直接传入了返回的promise2实例内部的resolve和reject方法),所以返回的promise2的状态完全都用户自己操作,如果执行了resolvePromise(y),则以值y为参数值修改promise2状态为已完成,如果执行了rejectPromise(r),则以值r为拒因修改返回的promise2状态为已拒绝。如果执行这个获取的x.then函数抛出异常e,则以e为据因拒绝返回的promise2。如果执行这个函数同时出现resolvePromise(y)、rejectPromise(r)、throw err三种其中的任意的组合。则谁先执行,就按最先执行的状态修改返回的promise2状态。如果在x.then这个三种都没有执行,则promise2一只会处于pending状态。(说明x.then为函数时,返回的promise2状态必须由用户自己去执行修改)。
    • 如果x不为对象或者函数,以x为参数将返回的promise2变为已完成状态(重要且常⻅)

    五、Promise构造函数上的静态⽅法

    Promise.resolve(xx)

    返回⼀个promise实例,参数xx等同于前面规范解读中的返回值x,也就是说返回的promise实例状态有参数xx决定。(即promise解决过程),所以Promise.resolve(xx) 不一定返回已完成状态的promise实例。

    Promise.reject(r)

    会直接返回一个已拒绝的promise实例,拒绝原因为参数r。

    Promise.all([promise, ..., promise])

    返回一个promise实例,只有当参数数组中的所有promise对象都变为已完成状态时,返回的promise才是变为已完成状态,那么已完成状态的值就为所有promise参数完成的值的数组组合,且一一对应; 否则如果有一个拒绝了,返回的promise就是已拒绝的状态。拒绝状态的原因为参数promise中第一个拒绝的值。例如:

    var promise1 = new Promise((resolve, reject) => {
        setTimeout(() =>{
            resolve('promise1')
        }, 1000)
    })
    var promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('promise2')
        }, 2000)
    })
    
    var promise3 = Promise.all([promise1, promise2])
    
    promise3.then((val) => {
        console.log(val) // [ 'promise1', 'promise2' ]
    }, (e) => {
        console.log(e)
    })
    

    Promise.race([promise, ..., promise])

    返回一个promise实例,参数数组中的promise是竞争关系,谁最先修改状态(已完成或已拒绝),返回的promise将继承这个最先修改的状态和值(或原因)。例如:

    var promise1 = new Promise((resolve, reject) => {
        setTimeout(() =>{
            reject(new Error('promise1 error...'))
        }, 0)
    })
    var promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('promise2 fulfilled')
        }, 2000)
    })
    
    var promise3 = Promise.race([promise1, promise2])
    
    promise3.then((val) => {
        console.log(val)
    }, (e) => {
        console.log(e.message) // promise1 error...
    })
    

    六、基于promiseA+规范 手写一个CustomPromise

    function CustomPromise (handleFunc) {
        this._init(handleFunc)
    }
    CustomPromise.prototype = {
        constructor: CustomPromise,
    
        _init(handleFunc) {
            this.status = 'pending'
            this.value = undefined
            this.bindFulfilledList = []
            this.bindRejectedList = []
            handleFunc(this._triggerResolve.bind(this), this._triggerReject.bind(this))
        },
        _triggerResolve(value) {
            var that = this
            queueMicrotask(function () {
                if (that.status !== 'pending') return
                that.status = 'fulfilled'
                that.value = value
                that.bindFulfilledList.forEach(bindFulfilled => bindFulfilled(value))
                that.bindFulfilledList = []
            })
        },
        _triggerReject(reason) {
            var that = this
            queueMicrotask(function () {
                if (that.status !== 'pending') return
                that.status = 'rejected'
                that.value = reason
                that.bindRejectedList.forEach(bindRejected => bindRejected(reason))
                that.bindRejectedList = []
            })
        },
        then(onFulfilled, onRejected) {
            var that = this
            var promiseInstance = new CustomPromise(function(nextResolve, nextReject) {
                function bindFulfilled (value) {
                    if (typeof onFulfilled !== 'function') {
                        nextResolve(value)
                    } else {
                        try {
                            var res = onFulfilled(value)
                            disposeRes(res)
                        } catch (error) {
                            nextReject(error) 
                        }
                    }
                }
    
                function bindRejected (reason) {
                    if (typeof onRejected !== 'function') {
                        nextReject(reason)
                    } else {
                        try {
                            var res = onRejected(reason)
                            disposeRes(res)
                        } catch (error) {
                            nextReject(error) 
                        }
                    }
                }
    
                function disposeRes (res) {
                    if(res === promiseInstance) {
                        nextReject(new TypeError('Chaining cycle detected for promise'))
                        return
                    }
                    if(res instanceof CustomPromise) {
                        res.then(function (val) {
                            nextResolve(val)
                        }, function (e) {
                            nextReject(e)
                        })
                        return
                    }
                    if(typeof res === 'object' || typeof res === 'function') {
                        var then = res.then
    
                        if(typeof then === 'function') {
                            then.call(res, nextResolve, nextReject)
                        } else {
                            nextResolve(res)
                        }
                        return
                    }
                    nextResolve(res)
                }
    
                switch(that.status) {
                    case 'pending':
                        that.bindFulfilledList.push(bindFulfilled)
                        that.bindRejectedList.push(bindRejected)
                        break
                    case 'fulfilled':
                        bindFulfilled(that.value)
                    case 'rejected':
                        bindRejected(that.value)
                    default:
                        break
                }
            })
            return promiseInstance
        },
        catch(onRejected) {
            return this.then(null, onRejected)
        }
    }
    
    CustomPromise.resolve = function (value) {
        return new CustomPromise(function (resolve, reject) {
            resolve()
        }).then(() => value)
    }
    
    CustomPromise.reject = function (reason) {
        return new CustomPromise(function (resolve, reject) {
            reject(reason)
        })
    }
    
    CustomPromise.all = function (list) {
        return new CustomPromise(function (resolve, reject) {
            if (list.length < 1) {
                resolve([])
            } else {
                let array = new Array(list.length)
                let count = 0
                for(let [i, promiseInstance] of list.entries()) {
                    promiseInstance
                        .then(
                            res => {
                                array[i] = res
                                count++
                                if(count === list.length) {
                                    resolve(array)
                                }
                            },
                            e => {
                                reject(e)
                            }
                        )
                } 
            }
        })
    }
    
    CustomPromise.race = function (list) {
        return new CustomPromise(function (resolve, reject) {
            if(list.length < 1) return
            for (let [i, promiseInstance] of list.entries()) {
                promiseInstance
                    .then(
                        res => {
                            resolve(res)
                        },
                        e => {
                            reject(e)
                        }
                    )
            }
        })
    }
    

    起源地下载网 » promise A+规范及源码编写

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元