函数的执行分为同步和异步两种。
同步即为 同步连续执行,通俗点讲就是作完一件事,再去作另外一件事。
异步即为 先作一件事,中间能够去作其余事情,稍后再回来作第一件事情。
同时还要记住两个特性:1.异步函数是没有返回值的,return无论用哦 2.try{}catch(e){}不能捕获异步函数中的异常。前端
js在处理异步回调函数的状况有着愈来愈值得推崇的方法及类库,下面会依次介绍js处理异步函数的发展史,及源码解读。
(本文代码是运行在node环境中)node
let fs = require('fs'); fs.readFile('./1.txt','utf8',function(err,data){ console.log(data); })
若是只有一个异步请求,那用callback还好,可是相信大多数前端开发者都遇到过这两种状况:
a.一个异步请求获取到的结果是下一个异步请求的参数。(一直嵌套callback,代码很差管理会造成回调地狱);jquery
let fs = require('fs'); fs.readFile('./1.txt','utf8',(err,data)=>{ fs.readFile(data,'utf8',(err,data)=>{ console.log(data); }) })
b.发出两个请求,只有当两个请求都成功获取到数据,在执行下一步操做。es6
let fs =require('fs'); fs.readFile('./1.txt','utf8',(err,data)=>{ console.log(data); }) fs.readFile('./2.txt','utf8',(err,data)=>{ console.log(data); })
像相似这种状况,只有当读取到1.txt 和2.txt的文件的时候,咱们同时获取到两个异步请求的结果。咱们能够写一个计数器的函数,统一处理回调;promise
function after(time,callback){ let arr = []; return function(data){ arr.push(data) if(--time==0){ callback(arr); } } } //统一处理回调结果的回调传到after函数中。 let out = after(2,(res)=>{console.log(res)}); let fs =require('fs'); fs.readFile('./1.txt','utf8',(err,data)=>{ out(data); }) fs.readFile('./2.txt','utf8',(err,data)=>{ out(data); })
tips:并发
方便咱们更好的了解计数器的实现原理,咱们须要了解一个概念:高阶函数
高阶函数:能够把函数做为参数 或者 return返回出一个函数。
举个例子:异步
①.判断一个变量是否是属于一个类型:模块化
function isType(type,content){ return Object.protoType.toString.call(content) ==`[Object ${type}]` } let a = [1,2,3]; isType('Array', a) == true;
②.js数据类型有好多,咱们每次调用都要传入他的类型,麻不麻烦。因此咱们写一个方法,能够批量生成函数。函数
function isType(type){ return function(content){ return Object.protoType.toString.call(content) == `[Oject ${type}]` } } let isArray = isType('Array'); let a = [1,2,3] isArray(a);
前两种示例讲的是return返回一个函数,下面示例是一个预置函数及返回函数参数的结合示例(预置函数)。测试
③.场景加入我有一个函数,执行第三次的时候我想输出'我很可爱';日常咱们能够这样去实现:
let time =0; function say(){ if(++item==3){ console.log('我很可爱') } } say(); say(); say();
高阶函数实现的话:
function after(time,callback){ return function(){ if(--time ==0){ callback(); } } } function say(){ console.log('我很可爱'); } let out =after(3,say) out(); out(); out();
高阶函数实现了将计时任务与业务逻辑拆分,高阶函数的实现主要得益于做用域的查找。
在看完了上面的callback讲述,主要其实仍是讲述了callback的弊端:
a.回调地狱(callback没法解决)
b.并发请求,同时拿到结果(可经过计数器方式,可是太费劲,不太乐观)
这个时候duang~duang~duang~,ES6带着Promise来了~
Promise主要是es6提供的主要用于处理异步请求的一个对象,他可以很好的解决回调地狱以及并发请求。
在写promise源码以前,咱们先经过几个调用promise的示例,了解一下promise的一些原理及特性,这在咱们封装promise的时候可以起到很大的做用:
普通调用实例:
let fs = require('fs'); let p = new Promise(function(resolve,reject){ fs.readFile('./1.txt','utf8',(err,data)=>{ err?reject(err):resolve(data); }) }) p.then((data)=>{console.log(data)},(err)=>{console.log(err)});
1.promise实例能够屡次调用then方法;
p.then((data)=>{console.log(data)},(err)=>{console.log(err)}); p.then((data)=>{console.log(data)},(err)=>{console.log(err)});
2.promise实例能够支持then方法的链式调用,jquery实现链式是经过返回当前的this。可是promise不能够经过返回this来实现。由于后续经过链式增长的then不是经过原始的promise对象的状态来决定走成功仍是走失败的。
p.then((data)=>{console.log(data)},(err)=>{console.log(err)}).then((data)=>{console.log(data)})
3.只要then方法中的成功回调和失败回调,有返回值(包括undefiend),都会走到下个then方法中的成功回调中,而且把返回值做为下个then成功回调的参数传进去。
第一个then走成功: p.then((data)=>{return undefined},(err)={console.log()}).then((data)=>{console.log(data)}) 输出:undefiend 第一个then走失败: p.then((data)=>{console.log(1)},(err)={return undefined).then((data)=>{console.log(data)}) 输出:undefiend
4.只要then方法中的成功回调和失败回调,有一个抛出异常,则都会走到下一个then中的失败回调中;
第一个then走成功: p.then((data)=>{throw new Err("错误")},(err)={console.log(1)}).then((data)=>{console.log('成功')},(err)=>{console.log(err)}) 输出:错误 第一个then走失败: p.then((data)=>{console.log(1)},(err)={throw new Err("错误")).then((data)=>{console.log('成功')},(err)=>{console.log(err)}) 输出:错误
5.成功和失败 只能走一个,若是成功了,就不会走失败,若是失败了,就不会走成功;
6.若是then方法中,返回的不是一个普通值,仍旧是一个promise对象,该如何处理?
答案:它会等待这个promise的执行结果,而且传给下一个then方法。若是成功,就把这个promise的结果传给下一个then的成功回调而且执行,若是失败就把错误传给下一个then的失败回调而且执行。
7.具有catch捕获错误;若是catche前面的全部then方法都没有失败回调,则catche会捕获到错误信息执行他就是用来兜儿底用的。
p是一个失败的回调: p.then((data)=>{console.log('成功')}).then((data)=>{成功}).catche(e){console.log('错误')}
8.返回的结果和 promise是同一个,永远不会成功和失败
var r = new Promise(function(resolve,reject){ return r; }) r.then(function(){ console.log(1) },function(err){ console.log(err) })
以上是通过调用es6提供的promise,发现的一些特性,下面咱们会根据这些特性去封装Promise类。
一.咱们先经过初步了解的promise和简单的基本调用,简单的实现一个promise;
1.Promise支持传入一个参数,函数类型,这个函数每每是咱们本身发起异步请求的函数,咱们称它为执行器actuator,这个函数会在调用new Promise()的做用域内当即执行,而且传入两个函数一个resolve另外一个是reject做为参数;
2.promise对象支持.then()的方法,then方法支持两个参数一个为onFulfilled成功回调另外一个为onRejected失败回调;onFulfilled接受参数data为异步请求拿到的数据,onRejected接受的参数为捕获到的异常错误。
3.当异步回调成功时,执行resolve,而且把回调结果传给resolve函数。失败则执行reject,把异常信息传给reject函数。(这一步每每是在actuator执行器函数中咱们本身去控制执行的)
4.一个promise对象,执行了resolve,就不会在去执行reject。执行了reject,也不会在去执行resolve;
因此promise内部中有一个相似状态机的机制,它分为三种状态,建立一个promise对象,默认状态为'pending'状态,当执行了resolve,则该状态变为'fulfilled',若果执行了reject则该状态变为'rejected',因此咱们在then方法中须要根据状态做出判断;
5.promise对象已是成功状态或是失败状态时,均可以继续经过then传入函数,会经过当前的状态,来决定执行成功还失败,而且把结果或是错误传给相应的函数。因此咱们须要拿到的结果和捕获的错误。
function Promise(fn){ this.status = 'pending';//状态机 //一个promise支持执行多个then,因此须要一个池子把他的回调函数存储起来,统一遍历执行; this.onFulfilledCallbacks = []; this.onRejectedCallbacks =[]; //保存结果或者错误异常 this.result = '';//当前promise回调成功获取到的数据; this.reason = '';//当前promise失败的缘由 var self = this; function resolve(data){ //执行了reject就不能执行resolve,因此必须保证是pending状态; //当执行回调成功,在执行器调用resolve,咱们去遍历成功回调的池子,依次执行; //保存结果,而且将当前状态设置为'fulfilled' if(self.status=='pending'){ self.result = data; self.status = 'fulfilled'; self.onFulfilledCallbacks.forEach((fn)=>{ fn(data); }) } } function reject(err){ //执行了resolve就不能执行reject,因此必须保证是pending状态; //当执行回调失败,在执行器调用reject,咱们去遍历成功回调的池子,依次执行; //保存错误缘由而且将当前状态设置为'rejected' if(self.status=='pending'){ self.reason= err; self.status ='rejected'; self.onRejectedCallbacks.forEach((fn)=>{ fn(err); }) } } fn(resolve,reject) } Promise.prototype.then= function(onFulfilled,onRejected){ //若是当前promise对象成功状态,则直接执行onFulfilled回调函数,而且把拿到的已经保存的成功数据传进去。 if(this.status =='fulfilled'){ onFulfilled(this.result) } //若是当前promise对象失败状态,则直接执行rejected回调函数,而且把已经保存的补货失败的缘由传进去。 if(this.status =='rejected'){ onRejected(this.reason); } if(this.status == 'pending'){ this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } }
到目前为止咱们已经封装了一个简易版的promise了,咱们能够经过一些case去测试一下,是否知足上面所描述的特性。
let fs = require('fs'); let p = new Promise((resolve,reject)=>{ fs.readFile('./1.txt','utf8',function (err,data) { err ? reject(err):resolve(data); }) }); p.then(data=>{console.log(data)},err=>{console.log(err)}); p.then(data=>{console.log(data)},err=>{console.log(err)});
2、咱们简易版的promise类,已经初步实现了一些promise的基本特性;这一节咱们咱们简易版的promise进行改版,把promise的更复杂的功能增长进去。
1.当咱们调用promise时,传入的执行器会马上执行,执行器函数内部是一个同步的过程,咱们能够用try...catch捕获错误,而且应该直接调用失败的函数。
2.promise支持链式写法,then后面继续.then ,原理并非像jquery同样返回一个this;而是无论当前promise状态是什么,都返回一个新的promise对象,官方文档命名这个新的promise对象为promise2。
3.链式写法中第二个then中的回调走成功仍是走失败,取决于上一个then中返回的promise(就是promise2)对象的状态。 而 promise2对象的状态,是由第一个then的参数(成功回调函数或失败回调函数)的返回值决定的。若是返回的是一个值(包括返回的是undefined、""),则第二个then走成功;若是返回的仍旧是一个promise对象,那么promise2会等待返回的这个promise对象的回调结果而肯定promise2的状态值,若是回调结果拿到的是一个值(成功),那么promise2会将此值做为参数传入字节的reosolve中并执行,若是回调中抛出异常(失败),那么promise2会把异常传到reject中而且执行;
function Promise(fn){ this.status = 'pending'; this.onFulfilledCallbacks = []; this.onRejectedCallbacks =[]; this.result = ''; this.reason = ''; var self = this; function resolve(data){ if(self.status=='pending'){ self.result = data; self.status = 'fulfilled'; self.onFulfilledCallbacks.forEach((fn)=>{ fn(data); }) } } function reject(err){ if(self.status=='pending'){ self.reason= err; self.status ='rejected'; self.onRejectedCallbacks.forEach((fn)=>{ fn(err); }) } } try{ fn(resolve,reject) }catch(e){ reject(e) } } Promise.prototype.then= function(onFulfilled,onRejected){ //then方法什么都不传,也能够支持连续调用 onFulfilled = onFulfilled ?onFulfilled :function(data){ return data}; onRejected =onFulfilled ? onFulfilled :function(err){throw new Error(err)} let self = this; let Promise2;//声明primise2 if(this.status =='fulfilled'){ Promise2 = new Promise(function(resolve,reject){ //promise2的状态,决定下一个then方法中执行成功仍是失败。 //promise2的状态,是由第一个then的onFulfilled的返回值决定的。 //当咱们执行onFulfilled(咱们经过then方法传进来的本身的函数)的时候,是同步操做,须要经过trycatch捕获异常,若是发现异常就直接走下一个then的reject失败回调。 //promise官方文档规定,每个resolve或是reject回调的执行必须保证是在异步中执行,因此咱们强制加定时器,保证onFulfilled是异步执行的。 setTimeOut(function(){ try{ let x = onFulfilled(self.result); //获取到返回值,须要去解析,从而判断出promise2应该走失败仍是成功。 resolvePromise(Promise2,x,resolve,reject) }catch(e){ //执行reject,下一个then就会走失败 reject(e); } }) }) } if(this.status =='rejected'){ Promise2 = new Promise(function(resolve,reject){ setTimeout(function(){ try{ let x = onRejected(self.reason); resolvePromise(Promise2,x,resolve,reject) }catch(e){ reject(e) } }) }) } if(this.status == 'pending'){ Promise2 = new Promise(function(resolve,reject){ self.onFulfilledCallbacks.push(function(){ setTimeout(function(){ try{ let x = onFulfilled(self.result); resolvePromise(Promise2,x,resolve,reject); }catch (e){ reject(e) } }) }); self.onRejectedCallbacks.push(function(){ setTimeout(function(){ try { let x = onRejected(self.reason); resolvePromise(Promise2,x,resolve,reject) }catch (e){ reject(e); } }) }); }) } return Promise2; } function resolvePromise(promise2,x,resolve,reject){ //此处若是相等会爆出类型错误; if(promise2 == x){ reject(new TypeError('循环引用了')) } //若是x是对象或函数(引用类型的值),则须要进一步判断。(这块儿要想的多一些,由于x是开发人员写的函数返回的,第一个then中回调返回的) //若果x是一个普通值,则直接执行resolve,而且传给下个then的成功; //若是返回的是一个promise对象,则promise2则会等待返回的promise对象执行完成,若是执行完成后,看这个promise走的成功仍是失败,若是失败则抛出异常。若是成功则将获取的数据做为onFulfilled返回的结果,用于判断promise2走成功或者失败,由于返回的结果可能仍是promise对象,因此用递归去执行,知道拿到数据或者异常。(递归) //判断是否是promise对象,经过有没有then方法 //捕获异常是由于判断不严谨,存在then方法,可能也不是promise对象,调用它的then可能会报错。 let called =false; if(x!==null &&(typeof x =='object'|| typeof x =='function')){ try{ let then =x.then; if(typeof then =='function'){ //promise对象 then.call(x,function(y){ if(called)return; called = true; resolvePromise(promise2,y,resolve,reject) },function(err){ if(called)return; called = true; reject(err) }) }else{ //普通对象 resolve(x) } }catch(e){ if(called)return; called = true; reject(e) } }else{ resolve(x); } } 到此,Promise的大部分特性都已经具有了。可是Promise对象还有一些其余的方法,可供调用,好比说catch方法,还有他的私有属性all 、race、defferd,若是前面的Promise封装懂了,那这些方法就so easy了,下面会根据这些方法的功能一一进行封装,
1.all方法处理 并发请求,同时得到结果。一个失败,则失败,都成功,才算成功.这个时候咱们就想到前面咱们写的计数器的用法。
Promise.all([read('./1.txt'),read('./2.txt')]).then(res=>{console.log(res)}) Promise.all = function(promiseArray){ return new Promise(function(resolve,reject){ var result = []; var i=0; function processData(index,res){ result[index] = res; if(++i==promiseArray.length){ resolve(result) } } promiseArray.forEach((item,index)=>{ item.then(res=>{processData(index,res)},reject) }) }) };
2.race方法,Pomise.race,顾名思义“赛拍”,传入多个异步promise,只要有一个成功,则就成功,有一个失败则失败,后面也可跟then方法。
Promise.race = function(promiseArray){ return new Promise(function(resolve,reject){ promiseArray.forEach((item,index)=>{ item.then(resolve,reject); }) }) } Promise.race([read('./1.txt'),read('./5.txt')]).then(res=>{console.log(res)},err=>{console.log(err)})
3.生成一个成功的promise,把传入的参数,传入到then的成功回调中,该方法返回一个promise
Promise.resolve=function(value){ return new Promise(function(resolve,reject){ //promise规范 resolve和reject函数必须是在异步回调中执行 setTimeout(function(){ resolve(value); }) }) } Promise.resolve('123').then(res=>{console.log(res)})
4.生成一个失败的promise,把传入的参数,传入到then的失败回调中。该方法返回一个promise
Promise.reject = function(err){ return new Promise(function(resolve,reject){ setTimeout(function(){ reject(err); }) }) } Promise.reject('error').then(res=>{console.log(res)},err=>{console.log(err)})
5.catch托底捕获错误,这个方法是实例的共有方法,应该放到Promise的原型上,每个 promise实例均可以调用.它支持一个参数,该参数是以前全部的then中,并无失败回调,当发 生错误时,最后统一在catch中进行捕获
Promise.prototype.catch = function(calllback){ return this.then(null,callback) }
6.不少人都用过jquery的deferrd对象,他和promise的deffer对象很相似。promise的deferred对象只是对promise进行了一次封装
Promise.defer = Promise.deferred=function(){ var obj = {}; obj.promise = new Promise(function(resolve,reject){ obj.resolve = resolve; obj.reject = reject; }) return obj; } let fs = require('fs'); function read2 (url){ var deferr = Promise.deferred(); fs.readFile('./1.txt','utf8',(err,res)=>{ err?deferr.reject(err):deferr.resolve(res); }) return deferr; } read2('./1.txt').then(data=>{console.log(data)})
至此,一个完整的Promise.js封装完成,固然最后是须要模块化导出的,咱们采用CommonJS规范导出一个模块 采用
module.exports = Promise;