基础预热:你好,JavaScript异步编程---- 理解JavaScript异步的美妙前端
理解异步之美:Promise与async await(一)vue
经历了上一篇基础的Promise讲解后我以为你们对于promise的基本用法和想法就有必定了解了。(就是一种承诺哟)面试
下面咱们要去了解一下它的工做流程算法
下面是别人实现的总源码,(简单一看就能够)vue-router
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise(callback) {
this.status = PENDING;
this.value = null;
this.defferd = [];
setTimeout(callback.bind(this, this.resolve.bind(this), this.reject.bind(this)), 0);
}
Promise.prototype = {
constructor: Promise,
resolve: function (result) {
this.status = FULFILLED;
this.value = result;
this.done();
},
reject: function (error) {
this.status = REJECTED;
this.value = error;
},
handle: function (fn) {
if (!fn) {
return;
}
var value = this.value;
var t = this.status;
var p;
if (t == PENDING) {
this.defferd.push(fn);
} else {
if (t == FULFILLED && typeof fn.onfulfiled == 'function') {
p = fn.onfulfiled(value);
}
if (t == REJECTED && typeof fn.onrejected == 'function') {
p = fn.onrejected(value);
}
var promise = fn.promise;
if (promise) {
if (p && p.constructor == Promise) {
p.defferd = promise.defferd;
} else {
p = this;
p.defferd = promise.defferd;
this.done();
}
}
}
},
done: function () {
var status = this.status;
if (status == PENDING) {
return;
}
var defferd = this.defferd;
for (var i = 0; i < defferd.length; i++) {
this.handle(defferd[i]);
}
},
then: function (success, fail) {
var o = {
onfulfiled: success,
onrejected: fail
};
var status = this.status;
o.promise = new this.constructor(function () {});
if (status == PENDING) {
this.defferd.push(o);
} else if (status == FULFILLED || status == REJECTED) {
this.handle(o);
}
return o.promise;
}
};
复制代码
这是网上一份常见的Promise的源码实现我会对这个进行一个分析 (确定有人问为何不本身实现一个? 解:省时、网上太多了、本质仍是要了解思想)编程
first :设计模式
let promsie = new Promise((resolve,reject)=>{
doSomething()
})
根据这个构造函数,咱们须要实现两个方法,resolve、reject方法。
复制代码
second :数组
promise.then(res=>{
doSomethingRes();
},rej=>{
doSomenthingRej()
})
咱们要实现一个then方法,能够去根据不一样状态来执行不一样的函数。
复制代码
最基本的两个内容咱们已经肯定了。promise
话很少说开始分析代码。异步
// 首先声明三个状态,
// 状态的就是上一节说的:事情是进行中、已完成、失败了。
// 这三种状态遵循着PENDING->FULEFILLED 或者PENDING->REJECTED
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
// Promise构造函数接收u一个回调函数
function Promise(callback) {
// 新new出来的实例的status必定是PENDING.
this.status = PENDING;
// value是指当你事情完成、失败后内部保存的值
// 用法是resolve(42) 在then函数的res=>{dosomething()res就是42
}
this.value = null;
// defferd 字面意思推迟、是个数组,存放这个promise之后要执行的事件
// 类比发布订阅模式(观察者模式)。存放观察者在被观察者身上订阅的事件列表
// defferd内的事件存放着往后观察者要执行的事件。
this.defferd = [];
// setTimeout异步的去执行new 一个promise实例内执行的任务,不去阻塞主线程。
//这一段代码我有一点疑惑,new promise实例时,callback的执行并非异步的。
// 而这里选择异步的并非很合理。
// bind函数的做用。callback的参数如何指定?经过bind方法将函数函数柯里化(Currying 和一个NBA球星的名字同样很好记)
// 并且绑定函数的this执行。函数内的this都执行这个new 出来的promise实例
// 去指定函数执行时的参数,将resolve方法与reject方法强制作为callback的参数。
// 因此咱们写的回调函数,参数怎么命名均可以执行到对应的resolve与reject方法
setTimeout(callback.bind(this, this.resolve.bind(this), this.reject.bind(this)), 0);
}
复制代码
本身实现与官方Promise执行的对比。你们能够看一下这个setTimeout致使的执行顺序问题。因此阅读别人对各类功能实现时要学会对照的去看。
到这里伙伴们已经了解了咱们new 一个构造函数时都会作哪些事情。
1:对promise实例定义一个状态,值为PENDING。
2:给promise实例定义一个存放值的空间。
3:设置一个发布列表,在之后的指定时间发布其中的事件。
4:经过bind函数将callback柯里化,使callback执行时调用对应的resolve与reject方法,并执行callback
为何先说这三个方法。 由于resolve、reject是核心方法,不说都不行,但是resolve与reject完成要作的事情必须是then方法指定的因此三个方法之间关系密切。
// 在Promise的原型对象上指定这些方法。
// 这种作法有很大弊端、而且Promise源码也并非这么作的以后会进行分析
Promise.prototype = {
// 覆盖式的指定Promise的原型对象会致使constructor属性丢失
// 在这里进行填补,手动指定Promise原型对象上的constructor属性
constructor: Promise,
// resolve方法开始 接收一个结果(能够为空)
resolve: function (result) {
//更改状态为FULFILLED。
this.status = FULFILLED;
// 将result存放在以前构造函数中提到的存放结果的空间中
this.value = result;
// done方法。表示执行完毕(后面会继续将)
this.done();
},
// 与resolve方法相似 很少作解释
reject: function (error) {
// 状态更改
this.status = REJECTED;
this.value = error;
// 没有done函数,这块作法颇有问题下面会配图解释。
},
// then方法开始要好好讲讲
// success表示状态变成FULFILLED时要执行的函数
// fail表示状态变成REJECTED时要执行的函数
then: function (success, fail) {
// 声明一个对象来存放这些事件。
var o = {
onfulfiled: success,
onrejected: fail
};
// 获取当前promise的状态。
// 这个的意义是,咱们对promise实例执行then方法的时候有两种状况
// 一:promise实例的内容尚未执行完毕。二:promise实例内容已经执行完毕而且状态已经改变。继续下面。
var status = this.status;
o.promise = new this.constructor(function () {});
// 若是状态是PENDING,表示实例内容还未执行完毕。
// 说明then方法指定在某种状态要执行的事件是将来发生的如今并不执行。
// 因此将o放入defferd订阅列表中。
// 这个以前讲过defferd中的内容会在某个条件触发后会执行。
// 因此当promise实例内容还未完成就要把将来执行的方法放入订阅列表
if (status == PENDING) {
this.defferd.push(o);
// 对应以前说的状况二
// 状态变成如下两种状况怎么办?
// 订阅列表内不该该有当前状况的o。
// 因此要当即执行当前指定的事件,并且将来的任何状况下此次指定的事件也不会执行。
//因此不会放入defferd中
} else if (status == FULFILLED || status == REJECTED) {
// 执行handle函数
this.handle(o);
}
// then方法存在链式调用,then方法的返回值必须是一个promise对象
return o.promise;
}
};
复制代码
到这里咱们大致梳理清楚,resolve、reject、then方法的用处。
一:在new promise实例过程当中执行的callback函数,在函数执行的过程当中确定会调用resolve或者reject(两个都调用也可能)。当调用了resolve方法以后会改变promise的状态,存放结果。表示任务完成,执行done函数。(reject就再也不来一遍了)
二:then方法的执行事件与resolve方法没有任何前后顺序可言。为所欲为谁在前面都不必定。在resolve(reject)以前执行,就注册一下要执行的事件。在resolve(reject)以后执行就直接执行就能够了,而且不要注册。
同窗们按照代码执行的顺序,咱们应该先去看done方法。而后再看handle因此辛苦一下,先向下一点找到可爱的done方法。
// 看完done方法的朋友确定对我这种方式很闹心,保证大家看的热情嘛。哈哈哈哈哈
// 没看done方法的快回去看、快回去看。
// 必须提一下 下面一直说的o是什么? o是在then方法中传入defferd数组中的对象,一下简称为o。
// handle是干吗的??? 那是用来执行o的。o里面放着咱们想要执行的内容
// 你们再回忆如下,handle还在哪里执行了?想起来了吧,当then方法执行时
// 若是状态已经改变了。那么就直接handle(o),执行你要作的事情。
handle: function (fn) {
// o不存在的???妈耶,咋办呀。那就是defferd中没东西。好吧什么都不作
if (!fn) {
return;
}
var value = this.value;
var t = this.status;
var p;
// 若是状态为PENDING,表示还没到o要执行的内容。那么不能执行的?
if (t == PENDING) {
this.defferd.push(fn);
} else {
// 这里面很容易看的,状态变成FULFILLED,
//而且你在给状态是FULFILLED时要作的事情能够执行(函数才能执行呀,你写个字符串不报错了??)
// 执行咯。这里面你们一看就知道,你then方法里面res=>{doSometthing(res)}
//这个res就是promise内存放的结果(value)。
if (t == FULFILLED && typeof fn.onfulfiled == 'function') {
p = fn.onfulfiled(value);
}
// 很少提了。
if (t == REJECTED && typeof fn.onrejected == 'function') {
p = fn.onrejected(value);
}
// 可是这个p是干什么的????
// 存放方法的返回值,为了链式调用。实现链式调用的是什么方法?
// 返回的o.promise. 那么返回的o.promise不存在呢?(固然这是不可能的)
// 那就没有链式调用。
// promise中有一个方法,当then函数内的事件(指的这个函数:res=>{})
// 返回值是一个promise对象时,那么then的返回值就是这个promise对象
// 链式中的下一个then就会等待这个promise执行完毕。
// 若是不是promise对象怎么办?那么就执行链条后面的then方法注册的事情。
var promise = fn.promise;
if (promise) {
// 若是你注册的事件执行后的返回值是一个promise对象
if (p && p.constructor == Promise) {
// 当前这个p(是个promise对象)能够把o.promise对象订阅列表内的事件拿过来。
// 串行后,返回的promise继续控制着defferd的内容。
//理论上讲,按刚才的逻辑来写,每一个promise对象内的defferd,都应该只有一个值。
// 由于串行的链条每一个then注册的事件都在上一个then返回的o.promise的defferd内。
// 那么为何?defferd要写个数组呢???这是我疑惑的地方可是影响不大
p.defferd = promise.defferd;
} else {
// 若是不是promise呢?那么就把当前的promise对象看成你的返回值
// 继续继承o.promise里面的defferd
p = this;
p.defferd = promise.defferd;
// 并无任何承诺,p应该就是一个resolved(reject)状态
// 直接执行done方法就能够啦。
this.done();
}
}
}
},
// done方法是在resolve方法(reject为啥没写以前有讲过)中执行的,表示resolve方法执行完毕了。
// 这个完毕是一种明确信号,那就是以前说好的状态变成FULFILLED我要作的那些事情。
// 乡亲们、弟兄们我们能够被执行了
done: function () {
// 固然为了保险起见PENDING状态确定不会执行,return掉。
// done函数在PENDING状态也不会被调用呀。双重保险嘛
var status = this.status;
if (status == PENDING) {
return;
}
// 乡亲们、兄弟们在哪呢?就在你的defferd中。咱们的订阅列表内的兄弟们该执行了。
// 遍历如下咱们的defferd对象,谁也别漏下都给我执行了。
var defferd = this.defferd;
for (var i = 0; i < defferd.length; i++) {
// 问题来了乡亲们不是函数是一个对象啊。
//为啥是对象?在then方法中有defferd.push(o);o是啥?是个对象啊。
// 那咋执行????须要一个特定来执行o的方法,就是handle。
// 好了伙伴们能够把文章回到上面一点了。
this.handle(defferd[i]);
}
},
复制代码
这一段我已经把handle与done方法说完了。主要是为了链式调用。才会设计的这样子。因此链式调用仍是很抢手的一个功能。 本身也能够尝试的去实现一下符合promise规范的promise功能。
不知道看到这里你们对网上常见的promise源码实现有一种什么样的感受???
看过源码(抱歉个人智商是在有限,短期内是真的看不懂啊),以为源码作的要合理太多了。看不懂我都以为合理。。。。不是对强者的过度崇拜,而是真的很合理。网上常见的实现,只是单单的实现了功能,这样的promise只适合有必定promise经验而且守规矩的人使用。为何这么说???
一:这样实现的promise,状态能够随时人为的更改,对外暴露,没有设置为私有属性。
二:为了方便,选择把方法设置在原型链上,致使没法使用私用变量。
三:reject的执行不足,只是对resolve进行合理的使用。
promise的源码则是(某个版本的,版本号我不记得了)
把resolve、reject、all、race,handle方法,都放在构造函数内。
把catch、then、chain方法放在原型上。
对比源码以后,以为本身虽然流程大致了解,可是这种精密并且优雅的方式,是短期内很难去掌握的。promise的源码固然会坚持看下去,网上能把promise按照规范实现一遍的人已经很厉害了。我虽然以为还有地方能够修改,可是我比他们还差的远(这种感受就有点像:我不上,我就比比),要向他们学习,照这他们去努力。
前端的学习之路还很漫长,我看过的(仅仅是看过的)源码半只手就都数的过来。仍是坚信坚持下去,本身就变得很棒。每一个人都是从控制流语句学过来的,逻辑也不过是复杂的控制流程(还涉及高端的算法与设计模式),坚信本身必定能够成功!!!一块儿努力吧 每个前端er(boy and girl)。因此一切源码层面看不懂、不理解均可以归结为看得少、想得少、理解的少。(和你的智商没有任何关系哟)
下一篇就是理解异步之美的终点篇了。异步的美好在于这种神奇的思想。抓住思想的尾巴,不被技术束缚,嘿嘿嘿。
要开新课题了。课题应该是围绕着vue-router的源码进行学习。一个与你们分享学习过程的周期性文章。尽情期待!!!