Javascript语言的执行环境是“单线程”。所谓“单线程”,就是一次只能完成一个任务。如果有多个任务,则必须排队,完成上一个任务,然后执行下一个任务,依此类推。这种模式的优点是实现起来比较简单,执行环境也比较简单;缺点是只要一个任务耗时长,后面的任务就必须排队等候,会延迟整个程序的执行。常见的浏览器无响应(假死),往往是因为某段Javascript代码运行时间过长(比如死循环),导致整个页面卡在这个地方,无法执行其他任务。为了解决这个问题,Javascript语言将任务执行方式分为同步(Synchronous)和异步(Asynchronous)两种。异步模式编程有4种方法:回调函数、事件监听、发布/订阅、Promises对象。还有generator,async/await。本文试图谈谈对Promise的理解以及如何实现。1.Promise的原理Promise对象分为三种Status,pending,fulfilled和rejected。Promise对象内部存储了一个需要执行一段时间的异步操作。当执行异步操作时,可以调用resolve或reject方法来改变promise对象的状态。状态一旦改变,就不能再改变。new在promise之后,可以使用then方法指定resolved和rejected时的回调函数。下面是我们日常使用Promise的代码逻辑。让p=newPromise((resolve,reject)=>{$.ajax({url:"../www/data.txt",dataType:"json",success(data){resolve(data);},error(err){reject(err);}});});p.then(function(data){alert("success"+data);},function(err){alert("failed");})结合PromiseA+规范,我们可以分析一下我们要实现什么:实现一个状态机,一共有三种状态,pending,fulfilled,rejected,状态之间的转换只能是pending->fulfilled,pending->rejected,状态改变是不可逆的。实现一个then方法,该方法可用于设置成功和失败回调。then方法必须被多次调用,所以then方法每次都需要返回一个新的promise对象,这样才能支持链式调用。构造函数内部必须有一个值,用来保存上次执行的结果值。如果报错,则保存异常信息。2.实现原理2.1状态机的实现下面我们将按照上述的原理和规范来实现Promise构造函数。classmyPromise{constructor(executor){this.status=PENDING;this.value='';this.Resolve=this.resolve.bind(this);this.Reject=this.reject.bind(this);this.then=this.then.bind(this);执行者(this.Resolve,this.Reject);}resolve(value){if(this.status===PENDING){this.value=value;this.status=FULFILLED;}}reject(value){if(this.status===PENDING){this.value=value;this.status=已拒绝;}}then(onfulfilled,onrejected){if(this.status===FULFILLED){onfulfilled(this.value);}if(this.status===REJECTED){onrejected(this.value);}}}constPENDING='pending';constFULFILLED='fulfilled';constREJECTED='rejected';consttest=newmyPromise((resolve,reject)=>{resolve(100);});test.then((数据)=>{console.log(data);},(data)=>{});因为Promise是构造函数,使用ES6的写法,首先想到的是上面显式构造函数声明的类,就是我们使用类的实现,我们可以看到我们实现了这个状态机,有status,value两个属性和resolve,reject,then三个函数;同时,它有pending、fulfilled和rejected三种状态,其中pending可以切换为fulfilled或rejected。运行一下,输出是100,但是现在不是异步处理的方案。该代码首先运行resolve(100),然后运行??then函数。实际上,它不处理异步情况。不信,加个setTimeout解决,就可以了,代码不再输出。2.2实现异步设置状态作为一个异步处理函数,在使用的时候,首先要设置好不同异步返回后的处理逻辑(即then调用函数的成功和失败),然后等待异步执行安心,最后异步结束后,系统会根据我们的逻辑自动选择调用不同的回调函数。也就是说,then函数需要处理状态为pending的状态。处理的原理是设置两个数组,在pending状态下保存成功和失败的回调函数,当状态发生变化时,根据状态调用保存在数组中的回调函数。classmyPromise{constructor(executor){this.status=PENDING;this.value='';this.onfulfilledArr=[];this.onrejectedArr=[];this.resolve=this.resolve.bind(this);this.reject=this.reject.bind(this);this.then=this.then.bind(this);executor(this.resolve,this.reject);}resolve(value){if(this.status===PENDING){this.value=值;this.onfulfilledArr.forEach(item=>{item(this.value);})this.status=FULFILLED;}}reject(value){if(this.status===PENDING){this.value=value;this.onrejectedArr.forEach(item=>{item(this.value);})this.status=REJECTED;}}then(onfulfilled,onrejected){if(this.status===FULFILLED){onfulfilled(this.value);}if(this.status===REJECTED){onrejected(this.value);}if(this.status===PENDING){this.onfulfilledArr.push(onfulfilled);this.onrejectedArr.push(onrejected);}}}constPENDING='pending';constFULFILLED='fulfilled';constREJECTED='rejected';consttest=newmyPromise((resolve,reject)=>{setTimeout(()=>{resolve(100);},2000)});test.then((data)=>{console.log(data);},(data)=>{});可以理解newmyPromise有异步代码setTimeout(()=>{resolve(100);},2000)js是单线程的,这个时候会先把这个任务添加到定时触发线程中去(添加到事件中计时结束后排队,等待js引擎空闲后再执行),先执行下面的同步代码test.then((data)=>{console.log(data);},(data)=>{});完成输出和状态变化,但是Promise的一大特点是可以链式调用,即test.then(success,fail).then(success,fail)...这个需要then返回一个新的Promise对象,而我们的程序现在显然不支持它。然后继续改变它。2.3实现链式调用,再次观察链式调用。如果success和failure函数中有返回值,这个值应该作为参数传递给下一个then函数的success或failure回调。所以我们需要在返回的newPromise中调用相应的函数。classmyPromise{constructor(executor){this.status=PENDING;this.value='';this.onfulfilledArr=[];this.onrejectedArr=[];this.resolve=this.resolve.bind(this);this.reject=this.reject.bind(this);this.then=this.then.bind(this);executor(this.resolve,this.reject);}resolve(value){if(this.status===PENDING){this.value=值;this.onfulfilledArr.forEach(item=>{item(this.value);})this.status=FULFILLED;}}reject(value){if(this.status===PENDING){this.value=value;this.onrejectedArr.forEach(item=>{item(this.value);})this.status=REJECTED;}}then(onfulfilled,onrejected){if(this.status===FULFILLED){constres=onfulfilled(这个值);returnnewPromise(function(resolve,reject){resolve(res);})}if(this.status===REJECTED){constres=onrejected(this.value);returnnewPromise(function(resolve,reject){reject(res);})}if(this.status===PENDING){constself=this;returnnewPromise(function(resolve,reject){self.onfulfilledArr.push(()=>{constres=onfulfilled(self.value)resolve(res);});self.onrejectedArr.push(()=>{constres=onrejected(self.value)reject(res);});})}}}constPENDING='pending';constFULFILLED='fulfilled';constREJECTED='rejected';consttest=newmyPromise((resolve,reject)=>{setTimeout(()=>{resolve(100);},2000)});test.then((data)=>{console.log(data);返回数据+5;},(data)=>{}).then((data)=>{console.log(data)},(data)=>{});再次运行,输出100、105,一个简单的Promise就完成了。
