- 博客: https://blog.youkuaiyun.com/qtfying
- 掘金: https://juejin.im/user/57739929c4c9710055376671
- QQ: 2811132560
- 邮箱: qtfying@gamil.com
最近被这三个兄弟搞得是晕头转向,然后决定花一番功夫比较深入的去了解一番,本着做为一名资深前端老油条的善良初心,决定还是布道,对还是布道一下,不但是加深自己对知识的理解,更是为了让后来的萌新们少走弯路,不用大费周折的去查阅各种资料,如果有幸看到这篇文章,那我恭喜您,这一篇文章我敢保定你能搞定这三者之间的关系~~~
Promise
产生的原因
解决原来ES5中的回调地狱
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
Promise的三种状态
- pending 进行中[待定]
- fulfilled 已成功[实现]
- rejected 已失败[被否决]
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
- 当promise状态发生改变,就会触发then()里的响应函数处理后续步骤;
- promise状态一经改变,不会再变。
- Promise对象的状态改变,只有两种可能:
- 从pending变为fulfilled
- 从pending变为rejected。
这两种情况只要发生,状态就凝固了,不会再变了。
Promise的用法
Promise对象是一个构造函数,用来生成Promise实例,用法很简单,new Promise(Fun)
既然Promise是个构造函数,接受一个带有resolve和reject两个参数的函数作为参数。既然是构造函数,肯定会想到原型(Prototype)方法和实例方法
let fun = function(resolve, reject) {
if (/* 异步操作成功 */) {
resolve(value);
} else {
reject(error);
}
}
const promise = new Promise(fun);
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
怎么拿到promise的值呢
promise.then(function(){})
.catch(function(){})
这样的话是不是很清晰明了,现在你可能还很迷惑,Promise到底解决了啥,他的优势在哪里呢,客官姑且往下看:
我们拿一个很经典的ajax请求来说,原来我们是怎么做呢:
$.ajax({
url: 'url',
dataType: 'json', //数据类型
type: 'GET',
timeout: 2000, //2s后超时
success: function() {
// 根据外层请求的结果,然后再次发送请求,循环嵌套
$.ajax({
url: 'url',
success: function() {}
})
},
error: function() {
if(textStatus==='timeout'){
alert('請求超時');
}
}
})
看到上面代码中的例子是不是有点熟悉,很常见,网上说这叫什么 — 回调地狱 , 一般情况我们一次性调用API就可以完成请求。
有些情况需要多次调用服务器API,就会形成一个链式调用,比如为了完成一个功能,我们需要调用API1、API2、API3,依次按照顺序进行调用,这个时候就会出现回调地狱的问题
注: 个人感觉倒是觉得还好,最多代码楼层太多,看起来不优雅而已。实际情况中我好像也没用碰到太多层的情况,不过好像还是很容易理解的哦
总有人说不优雅,那优雅的写法是什么样的呢,比如:
var _ajax = $.ajax('http://...');
_ajax.ifSuccess(success)
.ifFail(fail);
如果这样写的话就简单明了,经过万千程序员的不懈努力,ES6终于接收并原生实现了改方法,接下来咱们看看Promise这厮是怎么处理这个逻辑的,我拿一个登陆逻辑为例:
// 函数1:判断用户登陆成功
let userLogin = (res) => {
var user = new promise((resolve, reject) => {
$.ajax({
url: 'url1',
type: 'get',
data: { userName: 'Virgo', userPwd: '123456' },
success: res => {
resolve(res); // 请求成功则转成Promise对象并判断为resolve状态
},
error: err => {
reject(err.status); // 请求失败则转成Promise对象并判断为reject状态
}
})
return user;
}
// 函数2:取出对应登录用户的信息
let getUserInfo = (res)=> {
var userInfo = new Promise((resolve, reject) => {
$.ajax({
url: 'url2',
type: 'get',
data: { status: 'success to finish getUserInfo!' },
success: res => {
resolve(res); // 请求成功则转成Promise对象并判断为resolve状态
},
error: err => {
reject(err.status); // 请求失败则转成Promise对象并判断为reject状态
}
})
}
// 数据获取
userLogin().then((v1) => {
console.log(v1)
return getUserInfo('fun2')
}).then((v2)=>{
console.log(v2)
}).catch(()=> {
// 捕获到错误
})
看到这里,大家是否看的明白,我们稍微改造了下fun1、fun2、fun3.让他们都返回的了Promise对象,之后调用then方法,并传入回调,then中的回调方法,就是resolve。Promise让所有的回调全部用了then进行传入,这种链式写法,看上去会清晰些。
注:借用别人一句话,我看来看去还是觉得回调式更舒服,我仿佛是个智障
总结以下Promise的优势
- promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
- 并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据
- 代码风格,容易理解,便于维护
- 多个异步等待合并便于解决
- 避免回调地域,这个看你怎么理解了啊
具体的关于Promise的详细用法,由于本篇文章着重讲的是Promise和Generate、Async/wait 的区别,我在这里就不着重展开,大家要Promise的细节,请移步:
阮一峰-ECMAScript 6 入门之Promise
廖雪峰的官方网站-Promise
Generator
像写同步代码一样写异步 — 这个函数就像一个忍者,忠诚而可靠,行动全靠指令,没有命令,绝不越雷池半步
Generator介绍
Generator 的中文名称是生成器,它是ECMAScript6中提供的新特性。在过去,封装一段运算逻辑的单元是函数。函数只存在“没有被调用”或者“被调用”的情况,不存在一个函数被执行之后还能暂停的情况,而Generator的出现让这种情况成为可能。
通过function*来定义的函数称之为“生成器函数”(generator function),它的特点是可以中断函数的执行,每次执行yield语句之后,函数即暂停执行,直到调用返回的生成器对象的next()函数它才会继续执行。也就是说Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数返回一个遍历器对象(一个指向内部状态的指针对象),调用遍历器对象的next方法,使得指针移向下一个状态。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
yield
真正让Generator具有价值的是yield关键字,这个yield关键字让 Generator内部的逻辑能够切割成多个部分
还是那句话,你们网上能都搜到的,我这里就不再啰嗦了,本篇文章着重介绍三者之间的关系,请往下看:
let compute = function* (a, b) {
let sum = a + b;
yield console.log(sum);
let c = a - b;
yield console.log(c);
let d = a * b;
yield console.log(d);
return;
};
上面的代码中,被关键字 yield分割成3个模块,无限细分,可以这样理解,
把computed函数看作一辆火车,每个yield就是中间的每一做火车站,铁路局按照每条线路编制好每条线路的运行线路,按照事先预计的线路有规则有秩序的运行,每到一个火车站要做一系列的事情,我把这个事情形象的展示一下:
let G1233 = function* (a, b) {
let 上海 = function() {
对列车全检、将本列车将要销售的鸡腿哈根达斯装车、水箱注水等等
};
yield 上海段情况;
let 阜阳 = function() {
因为要出上海铁路局管辖范围则需要更换火车头、再次注水、办理补票、扔垃圾、补充本地土特产、对阜阳美景进行宣传等等
};
yield 阜阳段情况;
let 西安 = function() {
列车到站进行列车清理、列车检修、驾驶员换班、开始返程等等
};
yield 西安段情况;
return;
};
return
看上面的代码,每个Generator函数最终都会有一个return,什么意思呢,专业术语叫做,每个yield收到控制权后,必须要用return交出控制权,不然后续的next就拿不到数据
next
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
我们还拿上面的高铁G1233举例,我们想拿到上海段情况,怎么办呢,很简单G1233.next()
G1233.next() // 拿到上海段列车运行工作情况
G1233.next() // 拿到阜阳段列车运行工作情况
G1233.next() // 拿到西安段列车运行工作情况
G1233.next() // {value: undefined, done: true} 铁路局就安排了这三段工作,该给的已经给了,交差!!!完成了[done: true]
for … of
我擦,牛逼牛逼,一条线路也不止3站吧,获取20站总不可能一直next()下去吧,😃😀😃😀😃😀😃😀😃😀😃😀😃😀😃😀😃😀😃😀😃😀😃😀😃😀😃😀😃😀
哈哈,对了,可以迭代的,generator和Promise一样也是个对象,对象就可以迭代,for … of嘛
for (var x of G1233()) {
console.log(x); // 依次输出上海的、阜阳的、西安的
}
码字千千万,写到这里,一天也就过去了,容我暂时止笔,且听我下次娓娓道来~~~
由于篇幅过长,客官若还在,请移步至下文,故事更精彩