Promise是ES6中的一个标准了,我们使用一般用Promise来进行网络数据请求
下面总结一下Promise的用法和实现:
1.Promise的用法
1.使用Promise类包装一个异步的请求,一般是数据请求,可以用在网络请求,数据库请求,IO读写等等只要是异步的请求都行,比如我们用redis和mysql可以把数据库返回的数据包装成Promise对象,我们用ajax或者原生fetch或者axios发送http请求。比如我们用axios库,封装了一个get方法,
export const get = (url, params) => {
return new Promise((resolve, reject) => {
axios.get(queryString(`${baseUrl}/api${url}`, params))
.then(resp => {
resolve(resp.data)
}).catch(reject)
})
}
export const post = (url, data) => {
return new Promise((resolve, reject) => {
axios.post(`${baseUrl}/api${url}`, data)
.then(resp => {
resolve(resp.data)
})
.catch(reject)
})
}
然后我们还可以继续对这个get方法封装成更高级的方法,在不同的页面里,我们都需要这个get方法,我们可以根据不同页面的需要,封装不同的fetchdata函数:
@action fetchTopics(tab) {
return new Promise((resolve, reject) => {
if (tab === this.tab && this.topics.length > 0) {
resolve()
} else {
this.tab = tab
this.topics = []
this.syncing = true
get('/topics', {
mdrender: false,
tab,
}).then(resp => {
if (resp.success) {
const topics = resp.data.map(topic => {
return new Topic(createTopic(topic))
})
this.topics = topics
this.syncing = false
resolve()
} else {
this.syncing = false
reject()
}
}).catch(err => {
reject(err)
})
}
})
}
然后我们还是返回一个Promise对象,在需要使用的地方,我们使用then方法去解析,这里我们没有resolve出数据,因为我们这里是获取数据,然后保存到Mobx中的state里,所以我们在get后的then里,直接办response的data赋值给this.topics。这里的topic是observable,相当于我们在这里完成数据的获取,直接resolve()空就可以了,在ComponentDIdMount中,组件挂载的时候,我们调用一下这个函数就可以完成数据的获取了,在组件里,我们从this.props.topicStore.topics就可以获取到这些数据了, 然后解析对象,进行React渲染
Promise还可以用来包装mysql和redis的数据,为调用con.query这个mysql包提供的mysql查询方法返回的数据包装个Promise,
在需要用到的地方,使用then解析
//第一步,mysql查询,返回Promise包装的数据
function exec(sql) {
const promise = new Promise((resolve, reject) => {
con.query(sql, (err, result) => {
if (err) {
reject(err)
return
}
resolve(result)//将promise.value = result,promise.status = 'fulfiiled'
})
})
return promise
}
//第二步,controller层,拼接mysql语句,返回特定的mysql语句查询的Promise结果
const getDetail = (id) => {
const sql = `select * from blogs where id='${id}'`
return exec(sql).then(rows => {
return rows[0]
})
}
//第三步,不同路由下,传参不同,再包装一次做路由处理
if (method === 'GET' && req.path === '/api/blog/detail') {
// const data = getDetail(id)
// return new SuccessModel(data)
const result = getDetail(id)
return result.then(data => {
return new SuccessModel(data)
})
}
//第四步,主serverhandle中,完成数据的最后处理
const blogResult = handleBlogRouter(req, res)
if (blogResult) {
blogResult.then(blogData => {
if (needSetCookie) {
res.setHeader('Set-Cookie', `userid=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`)
}
res.end(
JSON.stringify(blogData)
)
})
return
}
我们看到,从最开始的原始数据,用Priomise包装后,我们每次都使用then方法去链式调用之前一个then方法处理过的数据,从原始的mysql的结果,到取出rows[0],再到用SuccessModel再做一次包装,再到最后使用then对上一个then返回的model做最后的response处理,结果response请求,最后不再return出数据,我们就走完了一个完整数据获取,进一步处理包装,再到返回给客户端,这样一个流程,多亏了Promise的then的链式调用(then方法会return this,后面会讲实现),我们可以很方便的使用promise做异步处理
同样我们也使用catch和reject进行错误的处理,如果一些response中有err的字段,我们可以检测,然后reject出去。
catch则是检测一些throw 的error,那么catch和reject的关系是啥呢,then方法其实有两个参数,第二个参数为啥一般没有用呢
我们看到,then可以实现链式的调用,resolve传入的参数可以被then方法继续调用,相比这个data肯定是赋值给了这个Promise实例的一个属性,比如;this.data,这些原理如何实现的呢,Primise还有race和all方法,这些方法又有什么用呢?Promise到底是怎么实现的呢,接下来介绍Promise的实现。
2.Promise的实现
1.Promise对象的基本结构,
new Promise((resolve,reject) => {}),
实例化一个Promise对象的时候,我们需要传入一个回调函数,resolve和reject是Promise类为回调注入的两个函数参数
,在回调里,我们会进行异步函数的处理,这里我们会使用一个 typeof 运算符,在Promise的构造函数里检测传入的是不是函数
Class Promise {
constructor(handle) {
if(typeof handle !== 'function'){
throw new Error ('Promise must accept a function as a parameter')
}
}
}
2.Promise的基本状态,Promise有三种状态,这是PromiseA+定义的,分别是:pending(进行中) fulfilled(成功) rejected(失败)promise的状态只能由pending编程fulfiied和rejected,改变后就不再发生变化,自然当我们调用resolve的时候,我们知道这个时候是成功了,我们就会把状态由pending改成fulfilled,reject方法则会改成rejected
class MyPromise {
constructor(handle) {
if(typeof handle !== 'function')
throw new Error('must accept a function as a parameter')
this.status = 'pending';
this.value = undefined;
//类的方法中使用了this,并且没有使用箭头函数的表示,那肯定要使用bind避免隐式绑定this丢失
try{
handle(this.resolve.bind(this),this.reject.bind(this))
} catch(err) {
this.reject(err)
}
}
resolve(val) {
if(this.status !== pending) return
this.status = 'fulfilled';
this.value = val;
}
reject(val) {
if(this.status !== pending ) return
this.status = 'rejected';
this.value = val;
}
}
3.then方法,then方法是有两个参数的!只不过一般我们只用第一个,第一个回调就是处理onfulfilled的,当status变为成功的时候,我们就调用then方法传入的第一个回调,变成rejeced的时候调用第二个回调,then方法可以调用多次,所以我们肯定要维护两个队列用来处理状态变成fulfilled和rejected时候的回调函数们,然后为了接下来可以继续调用,我们要返回一个新的promise对象。首先我们要添加两个回调函数队列到构造函数中,对应的是fulfilled和rejected,
this.fuifilledQueues = []
this.rejectedQueues = []
接着我们添加then方法:
then(onFulfilled,onRejected) {
//从当前promise实例中解析出value和status,传入两个函数参数,第一个是成功回调,第二个是失败回调
const {value ,status} = this;
switch(status) {
case 'pending':
this.fulfilledQueue.push(onFulfilled)
this.rejectedQueue.push(onRejected)
case 'fulfilled':
onFulfilled(value)
break
case 'rejected':
onRejected(value)
break
}
return new MyPromise((onFulfilledNext,onRejectedNext) => {})
}
返回一个新的promise了,那新的promise的状态是啥呢?这个状态就依赖于当前then方法执行的回调的情况和返回值,所以我们做一些完善。我们return出的Priomise也接受同样的两个回调,在这里就是两个Next
class MyPromise {
constructor(handle) {
if(typeof handle !== 'function')
throw new Error('must accept a function as a parameter')
this.status = 'pending';
this.value = undefined;
this.fulfilledQueue = [];
this.rejectedQueue = [];
//类的方法中使用了this,并且没有使用箭头函数的表示,那肯定要使用bind避免隐式绑定this丢失
try{
handle(this.resolve.bind(this),this.reject.bind(this))
} catch(err) {
this.reject(err)
}
}
resolve(val) {
if(this.status !== pending) return
this.status = 'fulfilled';
this.value = val;
}
reject(val) {
if(this.status !== pending ) return
this.status = 'rejected';
this.value = val;
}
then(onFulfilled,onRejected) {
//从当前promise实例中解析出value和status,传入两个函数参数,第一个是成功回调,第二个是失败回调
const {value ,status} = this;
return new MyPromise((onFulfilledNext,onRejectedNext) => {
let fulfilled = value => {
try{
if(typeof onFulfilled !== 'function'){
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if(res instanceof MyPromise) {
res.then(onFulfilledNext,onRejectedNext)
} else {
onFulfilledNext(res);
}
}
} catch (err) {
onRejectedNext(err)
}
}
let rejected = error => {
try{
if(typeof onRejected !== 'function') {
onRejectedNext(error)
} else {
let res = onRejected(error);
if(res instanceof MyPromise) {
res.then(onFulfilledNext,onRejectedNext)
} else {
onFulfilledNext(res)
}
}
} catch(err) {
onRejectedNext(err)
}
}
switch(status) {
case 'pending':
this.fulfilledQueue.push(onFulfilled)
this.rejectedQueue.push(onRejected)
case 'fulfilled':
onFulfilled(value)
break
case 'rejected':
onRejected(value)
break
}
})
}
}
然后我们在resolve和reject也要改成把队列里的回调都至此执行
_resolve (val) {
const run = () => {
if (this._status !== PENDING) return
this._status = FULFILLED
// 依次执行成功队列中的函数,并清空队列
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
// 依次执行失败队列中的函数,并清空队列
const runRejected = (error) => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
/* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
*/
if (val instanceof MyPromise) {
val.then(value => {
this._value = value
runFulfilled(value)
}, err => {
this._value = err
runRejected(err)
})
} else {
this._value = val
runFulfilled(val)
}
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
然后还有两个特殊的静态方法,一个是race一个是all
all方法是一个promise对象组成的数组,必须每个都成功,才返回冲的结果数组,否则只要一个失败,就直接reject出那个Promise的失败返回值
static all (list) {
return new Promise((resolve,reject) => {
let values = [];
let count = 0;
for(let [i,promise] of list.entries()){
this.resolve(p).then(res => {
values[i] = res;
count++
if(count === list.length) resolve(values)
},err => {
reject(err)
})
}
})
}
race方法,只要有一成功或者失败,就直接resolve或者reject
static race(list ) {
return new Promise ((resolve,reject) => {
for(let p of list) {
this.resolve(p).then((res) => {
resolve(res )
},err => {
reject(err)
})
}
})
}
还有一个就是finally方法,用于不管Promise最后状态,都会执行的方法
finally(cb) {
return this.then(
value => MyPromise.resolve(cb()).then(() => value),
reason => MyPromise.resolve(cb()).then(() => {throw reason})
)
}
以上加起来就是一个完整的Promise啦