updateTime: 2019-4-5 23:00
updateContent: async在项目中的使用简谈
前言
在单线程的js执行中,必然需要异步的出现来协程操作,否则就不用玩了, 而在js异步编程方案中,我们经历了回调地狱后终于推出了更合理强大的新方案,那就是——Promise,而在经历了co模块的洗礼后,es7顺利推出了Generator 的语法糖——Async(谁用谁知道,真滴爽=-=)
Promise 承诺
What
WhyThisName
根据promise对象的第一特征,除了异步操作可以影响其状态,其他外界操作都不能影响,表示一种承诺,即其他手段无法改变。
两个特征:
三个缺点:
3. 当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
How to use
const promise = new Promise((resolve, reject) => {
if (异步操作完毕) {
resolve(val)
} else {
reject(error)
}
})
promise.then((val) => {
// doSuccess
}, (err) => { // doFailure })复制代码
基于Promise的Ajax,告别回调地狱
手打一时爽..
// 手写一个ajax by promise
function _ajaxGet (url, params) { const promise = new Promise(function(resolve, reject){ const handle = function () { if (this.readyState == 4) { if (this.status === 200 || this.status === 304) { resolve(this.response) } else { reject(new Error(this.statusText)) } } } const XHR = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP') params = params || '' XHR.open('GET', url + params, true) XHR.onreadystatechange = handle XHR.responseType = 'json' XHR.setRequestHeader('Accept', 'application/json') XHR.send() }) return promise }
function _ajaxPost (url, data) { const promise = new Promise(function(resolve, reject){ const handle = function () { if (this.readyState == 4) { if (this.status === 200 || this.status === 304) { resolve(this.response) } else { reject(new Error(this.statusText)) } } } const XHR = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP') XHR.open('POST', url, true) XHR.onreadystatechange = handle XHR.responseType = 'json' XHR.setRequestHeader('Content-Type', '"application/json') XHR.send(data) }) return promise }
_ajaxGet('test.json').then(res => { console.log(res) }).catch(err => { console.log(err) })
_ajaxPost('test.json', {id: 123})复制代码
其实把上面的代码封装封装加一些其他的配置参数,就是一个简单的axios了(?我自己以为的),不过axios的原理就是通过promise实现的就没错了。
可以发现区别于传统的回调里面写异步,回调一层又一层,真的要好很多。
快速认识Promise对象的一些方法
1. Promise.all([p1,p2,p3])
类似于串联电路,
数组中的所有promise实例状态都为已定型(resolve)时,则返回一个所有实例的返回值数组进行该promise之后的回调,
若有一个/多个为reject则返回第一个reject的promise的error
demo(来自es6阮老师书中demo)
// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});复制代码
2. Promise.race([p1,p2,p3])
类似于并联电路,
返回多个实例中第一个进行了状态变换的实例的返回值作为回调函数的参数
3. 后续补充...
Promise内部一探
虽然没看源码,但猜测是用观察者模式实现的,通过改变状态值来触发对应回调函数(根据其特性加一些别的参数设置),后面出一期观察者模式与promise。
Promise的缺陷
假设有这样一个场景,五个动画,顺序加载到一个dom上,要求五秒内完成,否则提示报错并关闭动画。好了这个场景先不去实现,可想而知,为了实现第一个顺序加载,你需要
.then(res => {//anim1(); resolve()})
.then(res => {//anim2(); resolve()})
.then(res => {//anim3(); resolve()})
.then(res => {//anim4(); resolve()})
.then(res => {//anim5()})
代码越写越长,横纵变胖,贼难看,并且不容易察看
这个时候
Async Await(Generaor yiled)闪亮登场
今天先写到这。。。累,明天接着写
下面是目录
Generator 与 执行器 与 Thunk(携程函数)
Generator函数 function* helloworld () {} 利用Generator可以实现js的状态机
是不是很像c中的指针, 没错Generator函数调用后不会执行(交出函数执行权),但是会返回一个遍历器对象,通过对该对象的next(继续)方法调用,对函数内代码进行执行,每次next执行会在遇到的第一个yield(暂停)停下,然后返回一个键值分别为value,done的对象来表示当前暂停右侧表达式的值和遍历的状态。在多任务情况下,多个Generator(async:我是谁我在那)通过yield(await:我是谁我在哪,并且我遇到的不是异步我会立即执行再返回一个已定型的promise对象)来交换控制权。
一直执行继续(next)然后当遍历状态(done)为true时遍历结束,下次再执行就会一直返回
{value: undefined, done: true}
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
复制代码
代码终于看起来比回调清晰了,但是流程却有些不如意,那么如何自动执行呢,有请
Thunk 传名调用的一种实现策略
what: 计算机语言求值策略中的传名调用,即将表达式直接传入,减少不必要计算损失。编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);
// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
return function (callback) {
return fs.readFile(fileName, callback);
};
};
var readFileThunk = Thunk(fileName); 复制代码
readFileThunk(callback);
当然js是值传递调用!在js中Thunk起到的主要作用是多参变单参后将回调传回。上面看到将callback传回。
而在这里对Genrator的自动流程管理实现的帮助中Thunk起到的是对next指针传出后再传入上次yield表达式执行完毕地址,这样就可以可以把执行权在next传出后又重新传入,拿回执行权。
一句话,上次异步成功后自动继续yield后代码。
但是看起来好麻烦,还不如写写thenthenthen(Promise自动执行,变胖就变胖,口亨)是吧,还要写Genrator的执行器。这个时候Co闪亮登场。
Co 小轻美
var co = require('co');
co(gen);
// Generator函数传入co中自动执行复制代码
what: Thunk与Promise的结合糖
tip: 注意了co的实参只能是Thunk 函数或 Promise 对象。支持并发。
Async Await的优点
- 内置执行器
- 更好的语义
- 更广的适用性
- 返回值是 Promise
实例Demo与项目中应用简谈
1. 当进入首页组件前在app组件created中首先调用initUserInfo异步函数,该函数在store的index.js的dispatch中定义
2. 然后我们需要在首页组件加载时调用几个请求来渲染对应用户的数据,这几个请求需要使用userid这个数据
如果用promise来实现的话,那就是以下代码
methods: {
initUserInfo () {
return _axios.get(uri).then(res => { // 参考上面用promise实现的ajax.getJson
commit('userInfo', res.data.data) // 全局状态更新处理
})
}
initMyproInfo () {
// ...
return _axios.post(getProUri, {usrId: this.userInfo.id}) // ...mapGetters(['usrInfo'])
}
,
created () {
this.initUserInfo().then(res => {
console.log(res) // 打印获取到的用户数据
return this.initMyproInfo()
}).then(res => { // 这个回调依赖于调用他的promise对象状态
this.this._message.info(`data id is ${data.id}`)
}).catch(err => { // 捕捉then中第一个错误
console.log(err)
})
}
复制代码
//...mapActions(['initUserInfo'])
async initUserInfo ({commit}) {
await let {data} = _axios.get(uri)
commit('initUserInfo', data.data) // 全局状态更新处理
return data.data
}
async created () { // 是不是看起来清爽多了!
await this.initUserInfo()
let data = await this.initMyproInfo() // 正常结果下,await后是一个promise对象,返回该对象的结果,在本例中即返回pro请求后的结果res
this._message.info(`data id is ${data.id}`)
}复制代码
Last, 各位如果觉得还喜欢,对你理解有用,麻烦点个赞吧(给您鼓掌了!^_^)
如果有错误的地方,请您不吝指出,谢谢!