[JavaScript] Promise And Ajax/Axios And Async

2,704 阅读9分钟

updateTime: 2019-4-5 23:00 

updateContent: async在项目中的使用简谈

updateTime: 2020-1-5 

updateContent: 当aysnc下的await被包裹在花括号时..


前言

在单线程的js执行中,必然需要异步的出现来协程操作,否则就不用玩了, 而在js异步编程方案中,我们经历了回调地狱后终于推出了更合理强大的新方案,那就是——Promise,而在经历了co模块的洗礼后,es7顺利推出了Generator 的语法糖——Async(谁用谁知道,真滴爽=-=)

Promise 承诺

What

用于处理异步回调的一种解决方案,比传统回调更强大合理

WhyThisName

根据promise对象的第一特征,除了异步操作可以影响其状态,其他外界操作都不能影响,表示一种承诺,即其他手段无法改变。

两个特征:

1. 对象状态不受外界影响,三种状态为: pengding, fulfilled(已成功), rejected(已失败)
2. 状态改变后不在改变,只有两种改变: pending->fulfilled / pending->rejected

三个缺点: 

1. new后无法取消
2. 无回调则内部错误无法抛出

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

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与项目中应用简谈

最近使用vue做了一个项目类似于阿里的iconFont,首先用一个很简单的首页状态逻辑来介绍async的使用(顺序加载)

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)
    }) 
}

如果用async 来实现的话

//...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}`)
}


当aysnc下的await被包裹在花括号时

await到底暂停了哪里的代码,仅仅是花括号后的吗? 如果用promise then 角度来看,可能会以为就是暂停then 括号内的钩子函数,promise外的同步代码并不被影响,那么在async中这一招还行得通吗?

先来看这么一张图:


之前的我一直对await的理解不深刻,因为没有自己实现过await,只去简单实现了个对象上的promise,对await只是简单使用,扫了眼文档并没有记住其详细,所以用了简单的promise.then链式的思想去看待了await,结果当时有一次在项目里就出了糗。

先来看看MDN上对await的说明


会直接暂停!如果是以前的promise then方式,来看看会怎么样:


promise没有被暂停是因为其所在函数并没有被改装,所以,在想要改装函数为async来使用await时请看清逻辑场景抽出来使用!不然就会出现一不小心把后续同步语句也暂停的尴尬情况。

如果还有人没明白场景,我直接来叙说下我的糗事场景,一个组件前置守卫处,我进行了权限查询,如果用户无权限则跳转登陆页或者已登录时跳转无该权限页面,但是我的跳转是必然进行的无论是否查询到结果,都要next(true/false)。当时我使用了await结果就出现了问题,后来改回了promise才ok。(因为是放假,我拿不到内网代码提交记录,等我放假回去了路清楚再把这里重新描述一下)。 

反正要强调的一点就是,官档上说的第一句话: await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。

如果你不懂这句话,你写async中的 await代码外的代码可能会出现错误逻辑。


这里放上刚刚那段代码,懒得传github了,如果乱了自己编辑器格式化下

async function asyncDemo(canExecute) {
    console.time('asyncDemoStart')
    console.time('asyncDemoStartTwo')
    if (canExecute) {
        let res = await promiseFunc()
        console.log(res)
        console.timeEnd('asyncDemoStart')
    }
    console.log('i must be executed!')
    console.timeEnd('asyncDemoStartTwo')
}
function promiseFunc() {
    return new Promise((res) => {
        setTimeout(() => { res('ok, i will be excuted.') }, 1500)
    })
}
asyncDemo(1)
// promiseDemo Execute
function promiseDemo(canExecute) {
    console.time('promiseDemoStart')
    console.time('promiseDemoStartTwo')
    if (canExecute) {
        promiseFunc().then((res) => {
          console.log(res)
          console.timeEnd('promiseDemoStart')  
        })        
    }
    console.log('i must be executed!')
    console.timeEnd('promiseDemoStartTwo')
}
promiseDemo(1)


Last, 各位如果觉得还喜欢,对你理解有用,麻烦点个赞吧(给您鼓掌了!^_^)

如果有错误的地方,请您不吝指出,谢谢!