最近闲暇的时候在研究node。当我将文件夹中的文件简单复制到另一个位置时,我看到了所谓的回调地狱,虽然只是四五个回调巢,但这已经吓到我了。写了这么一个简单的小demo,就这样写了。是不是有点复杂?记得在看ES6的时候,提到了一种新的解决回调的方式---Promise,这个也是node中常用的解决大量嵌套的方法,所以这几天花时间看了看Promise,把我的对Promise的理解比较清楚,所以写点东西总结一下。Promise状态理解用newPromise实例化的Promise对象有三种状态:“has-resolution”——Fulfilledreslove(成功时),调用onFulfilled“has-rejection”——Rejectedreject(失败时)。CallRejected"unresolve"-Pending既不是resolve也不是reject状态,是Promise刚刚创建后的初始化状态。注意:在Chrome中输出resolve得到Promise{[[PromiseStatus]]:"pending",[[PromiseValue]]:undefined},可以看出[[PromiseStatus]]存储了Promise的状态,但不是public访问[[PromiseStatus]]的用户API,所以目前还不能查询其内部状态。Promise中的then回调只会被调用一次,因为Promise的状态只会从Pending变为Fulfilled或Rejected,这是不可逆的。Promise的使用使用Promise实现有序异步执行的基本格式如下://definedPromiseasyncfunctionfunctionasyncFun(){returnnewPromise((reslove,reject)=>{if(reslove){reslove(/*reslove参数*/);}else{reject(newError(/*Error*/));}})}//使用Promise&thenasyncFun().then(/*function*/).then(/*function*/)...reslove方法的参数是要传给回调函数的参数,即resolve会把运行得到的结果传过去,然后接受参数给回调继续执行。如果then中的函数返回Promise,就会重复执行这一步,直到结束。reject方法的参数一般是包含拒绝原因的Error对象。reject和resolve一样,也会传出自己的参数,then的第二个fun或者catch会接收到参数。实际上,.catch只是Promise.then(onFulfilled,onRejected)的一个别名。快速创建Promise一般情况下,我们使用newPromise来创建promise对象。另外,我们也可以直接使用Promise.reslove和Promise.reject来创建。例如,Promise.resolve(42)可以被视为以下代码的语法糖newPromise((reslove)=>{reslove(42);});这段代码可以让Promise对象立即进入resolve状态,将42传递给then后面指定的onFulfilled函数。另外,Promise.resolve还有一个功能,就是将非Promise对象转为Promise对象。Promise.reject(value)类似。Promise.then()异步调用带来的思考varpromise=newPromise(function(resolve){console.log("innerpromise");//1resolve(42);});promise.then(function(值){console.log(值);//3});console.log("外部承诺");//2/*output:"innerpromise""outerpromise"42*/从上面的代码我们可以看出Promise.then()是异步调用的,这在Promise的设计中也是有规定的。原因是同步调用和异步调用同时存在会造成混淆。如果上面的代码在调用onReady之前已经加载了DOM,回调函数会被同步调用。如果在调用onReady之前DOM尚未加载,则回调将通过注册DOMContentLoader事件监听器异步调用。这将导致代码在源文件的不同位置输出不同的结果。关于这种现象,有以下几点:绝对不要对异步函数进行同步调用(即使数据已经准备好)如果对异步回调函数进行同步调用,处理顺序可能与预期不符,这可能导致意想不到的结果。同步调用异步回调函数也可能导致堆栈溢出或异常处理混乱等问题。如果以后想调用异步回调,可以使用setTimeout等异步API。上面的代码应该用setTimeout(fn,0)调用。函数onReady(fn){varreadyState=document.readyState;if(readyState==='interactive'||readyState==='complete'){setTimeout(fn,0);}else{window.addEventListener('DOMContentLoaded',fn);}}onReady(function(){console.log('DOM完全加载和解析');});console.log('==开始==');sothen在Promise中是异步的。Promise链调用是相互独立的。如果要实现Promise链式调用,每个链式调用都必须返回Promise。因此,每次异步执行都需要用Promise进行包装。这里有一个误区:每次then和catch都会返回并返回一个新的Promise,但这只是实现了链式调用。如果异步操作没有用Promise包装,仍然无法正常工作。下面的例子是错误的。functionpro1(){returnnewPromise((reslove,reject)=>{if(reslove){setTimeout(()=>{console.log(1000)},1000);reslove();}})}functionpro2(){setTimeout(()=>{console.log(2000)},2000);}functionpro3(){setTimeout(()=>{console.log(3000)},3000);}pro1()。then(pro2).then(pro3);//函数pro1(){setTimeout(()=>{console.log(1000)},1000);}Promise.resolve().then(pro1).then(pro2).然后(pro3);上面的写法有两个错误:虽然在第一个函数中返回了一个Promise,但是由于后面的异步操作没有被Promise包裹,所以不会有任何效果。正确的做法是每一个异步操作都必须被Promise包裹起来。resolve()调用的时间错误。Resolve需要在异步操作执行完后调用,所以需要写在异步操作里面。如果像上面那样写在异步操作之外,是不行的。.所以正确的写法是://直接returnPromisefunctionpro1(){returnnewPromise((resolve,reject)=>{setTimeout(()=>{console.log(1000);resolve();},1000);})}functionpro2(){returnnewPromise((resolve,reject)=>{setTimeout(()=>{console.log(5000);resolve();},5000);});}functionpro3(){returnnewPromise((resolve,reject)=>{setTimeout(()=>{console.log(500);resolve();},500);})}pro1().then(pro2).then(pro3);//或使用Promise.reslove()functionpro1(cb){setTimeout(()=>{console.log(1000);cb()},1000)};functionpro2(cb){setTimeout(()=>{console.log(3000);cb()},3000)};functionpro3(cb){setTimeout(()=>{console.log(500);cb()},500)};Promise.resolve().then(()=>newPromise(resolve=>pro1(resolve))).then(()=>newPromise(resolve=>pro2(resolve))).then(()=>newPromise(resolve=>pro3(resolve)));每个Task都需要传递参数。在Promise的链式调用中,各个任务之间可能存在相互依赖关系。比如TaskA要给TaskB传递一个参数,如下:/*Example1.UsingPromise.resolve()start*/lettask1=(value1)=>value1+1;lettask2=(value2)=>value2+2;lettask3=(value3)=>{console.log(value3+3)};Promise.resolve(1).then(task1).then(task2).then(task3);//console=>7/*例子2.普通返回一个Promise*/functiontask1(value1){returnnewPromise((resolve,reject)=>{if(resolve){resolve(value1+1);}else{thrownewError("throwError@task1");}});}functiontask2(value2){返回新的Promise((resolve,reject)=>{if(resolve){resolve(value2+2);}else{thrownewError("throwError@task1");}});}functiontask3(value3){返回新的Promise((resolve,reject)=>{if(resolve){console.log(value3+3);}else{thrownewError("throwError@task1");}});}task1(1).then(task2).then(task3);//console=>7关于reslove和reject有两种解释:reslove函数的作用是将Promise对象的状态从“未完成”变为“成功”(即从PendingtoResolved),当异步操作成功时调用,将异步操作的结果作为参数传递;reject函数的作用是将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),异步操作失败时调用,将异步操作报错作为参数传递;所以从上面的例子和它们的用法可以看出,如果要传递给下面的任务,有两种方式:如果使用Promise.resolve()来启动Promise,那么在参数前面加上return需要像例1那样传递。如果使用Promise来包装任务,那么将要传递给下一个任务的参数传递到resolve()中。能。特别注意:如果resolve()后面需要传递多个参数,不能直接写resolve(a1,a2,a3),只能获取第一个传递的参数,需要以数组或对象的形式传递letobj={a1:a1,a2:a2,a3:a3};resolve(obj)//或者让arr=[a1,a2,a3];resolve(arr);然后catch在Promise中返回一个新的Promise是否是thenor的catch方法会返回一个新的Promise对象。varaPromise=newPromise(function(resolve){resolve(100);});varthenPromise=aPromise.then(function(value){console.log(value);});varcatchPromise=thenPromise.catch(function(error){console.error(error);});console.log(aPromise!==thenPromise);//=>trueconsole.log(thenPromise!==catchPromise);//=>truesochainlikethis写单独的方法调用是不成功的//1:在同一个promise对象上同时调用`then`方法时间varaPromise=newPromise(function(resolve){resolve(100);});aPromise.then(function(value){returnvalue*2;});aPromise.then(function(value){returnvalue*2;});aPromise.then(function(value){console.log("1:"+值);//=>100});由于每次调用then方法都会返回一个新的Promise,所以最终输出的结果会是100而不是10022。Promise.all()的使用有时需要多个互不相关的异步任务全部执行完执行后续操作。这时候就需要Promise.all(),它接收一个Promise对象数组作为参数,当这个数组中的所有Promise对象都变成resolve或者reject时,就会调用下面的.then()。这里需要说明一下,两个互不相关的异步操作会同时执行,每个Promise的结果(即每个返回的Promise被resolved或rejected时传递的参数)在与传递给Promise.all的Promise数组的顺序相同。也就是说,假设有两个异步操作TaskA和TaskB,如果传入的顺序是Promise.all([TaskA,TaskB]),那么执行后传给.then的顺序就是[TaskA,TaskB]。functionsetTime(time){returnnewPromise((resolve)=>{setTimeout(()=>resolve(time),time);})}letstartTime=Date.now();Promise.all([setTime(1),setTime(100),setTime(200)]).then((value)=>{console.log(value);//[1,100,200]console.log(Date.now()-startTime);//203});从上述函数的输出值可以看出,Promise.all()中的异步操作是同时执行的,传递给.then()的顺序与Promise.all()中的相同。最后的执行时间大概是200ms,为什么不是200ms呢,这个涉及到setTimeout的精确问题,这里不展开讨论。Promise.race()的使用Promise.rance()的用法与Promise.all()类似,区别在于Promise.all()会在接收到的所有Promise都变为FulFilled或Rejected后继续进行后续处理,而只要Promise对象进入FullFilled或Rejected状态,Promise.rance()就会继续执行后续处理。这相当于Promise.all()ANDing和Promise.rance()ORing。但这里有一点需要注意:vartaskA=newPromise(function(resolve){setTimeout(function(){console.log('thisistaskA');resolve('thisistaskA');},4);});vartaskB=newPromise(function(resolve){setTimeout(function(){console.log('这是taskB');resolve('这是taskB');},1000);});Promise.race([winnerPromise,loserPromise]).then(function(value){console.log(value);});/*输出结果:thisistaskAthisistaskAthisistaskB*/从这里可以看出,在第一个当Promise变为FulFiled状态并运行then中的回调后,后续的Promise并没有停止运行,而是继续执行。也就是说,Promise.race不会在第一个promise对象变为Fulfilled后取消其他promise对象的执行。Promise拒绝和异步操作错误理解函数ReadEveryFiles(file){returnnewPromise((resolve,reject)=>{if(resolve){fs.readFile(`${__dirname}/jQuery/${file}`,(err,data)=>{if(err){console.log(err);}else{letobj={data:data,file:file};resolve(obj);}});}else{//promiserejecterror}});}这里readFile的error和Promise的reject不同。一个是readFile过程中产生的错误,一个是Promise处理时产生的错误。可以这样理解,假设文件读取成功了,但是Promise仍然需要处理这个异步操作得到的数据,而Promise在执行这些操作的时候可能会出错。最近几天写的,开始用Promise写一些东西,发现如果用Promise,代码量会增加,因为每一个异步都要用Promise封装,但是换来的是更容易维护,所以值得它。当代码写好后,我们很容易看到代码的执行过程。比起原来的嵌套写法,直观多了。如果我们想解决Promise代码量过大的问题,我们可以使用Generator功能,另外ES7标准中引入了更强大的异步解决方案Async/Await。稍后我将继续深入了解它们。参考JavaScriptPromiseMiniBook(中文版)ECMAScript6入门---Promise对象
