一、为什么需要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)
}
)
}
})
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!