原文链接:浅谈NodeJS之Promise
NodeJS里为解决同步执行的问题,常常须要嵌套调用,Promise就是解决这一问题的,其实微信小程序开发也有同样的问题,也提供了promise形式的解决办法。
以下是援用的原文:
啥是Promise?
Promise是一个构造函数,办法有all、reject、resolve这几个,原型上有then、catch等办法。那么new Promise 进去的对象必定就有then、catch办法。跟着我一步一步学习吧。本文能让你对Promise有一个颠覆的意识。
让咱们new一个吧:
var promise = new Promise(function(resolve, reject){ //异步操作 setTimeout(function(){ console.log('执行结束!'); resolve('xxx'); }, 2000);});
执行异步操作,2秒后输入“执行结束!”,并调用resolve办法。
Promise的构造函数接管一个参数,是函数,并且传入两个参数:resolve,reject,别离示意异步操作执行胜利后的回调函数和异步操作执行失败后的回调函数。其实依照规范来讲,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected。
以上咱们new了一个对象,并没有调用它,传进去的函数就曾经执行了,所以咱们用Promise的时候个别是包在一个函数中,在须要的时候去运行这个函数,如:
function runAsync(){ var promise = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('执行结束); resolve('xxx'); }, 2000); }); return promise; }runAsync();
包装函数的作用和resolve的作用
执行这个函数咱们能失去了一个Promise对象,即包装好的函数最初,会return出Promise对象,。还记得Promise对象上有then、catch办法吧?这就是弱小之处了,看上面的代码:
runAsync().then(function(data){ console.log(data); //这里用传过来的数据做其余操作});
在runAsync()的返回上间接调用then办法,then接管一个参数,是函数,并且会拿到咱们在runAsync中调用resolve时传的的参数。运行这段代码,2秒后输入“执行结束”,紧接着输入“xxx”。
then外面的函数就跟咱们平时的回调函数一个意思,可能在runAsync这个异步工作执行实现之后被执行,这就是Promise的作用,艰深讲,就是把原来的回调写法分离出来,在异步操作执行完后,用链式调用的形式执行回调函数。
如果只有一次调用,也能够这么写,成果一样:
function runAsync(callback){ setTimeout(function(){ console.log('执行结束'); callback('xxx'); }, 2000);} runAsync(function(data){ console.log(data);});
那么有多层回调该怎么办?
Promise的劣势在于,能够在then办法中持续写Promise对象并返回,而后持续调用then来进行回调操作。
链式操作的用法
所以,从外表上看,Promise只是可能简化层层回调的写法,而本质上,Promise的精华是“状态”,用保护状态、传递状态的形式来使得回调函数可能及时调用,它比传递callback函数要简略、灵便的多。所以应用Promise的正确场景是这样的:
function runAsync1(){ var promise = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('异步1实现'); resolve('xxx1'); }, 1000); }); return promise ; }function runAsync2(){ var promise = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('异步2实现'); resolve('xxx2'); }, 2000); }); return promise ; }function runAsync3(){ var promise = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('异步3实现'); resolve('xxx3'); }, 2000); }); return promise ; }runAsync1().then(function(data){ console.log(data); return runAsync2();}).then(function(data){ console.log(data); return runAsync3();}).then(function(data){ console.log(data);});
这样可能按程序,每隔两秒输入每个异步回调中的内容,在runAsync2中传给resolve的数据,能在接下来的then办法中拿到。运行后果如下:
异步1实现xxx1异步2实现xxx2异步3实现xxx3
在then办法中,你也能够间接return数据而不是Promise对象,在前面的then中就能够接管到数据了,比方咱们把下面的代码批改成这样:
runAsync1().then(function(data){ console.log(data); return runAsync2();}).then(function(data){ console.log(data); return '间接返回数据'; //这里间接返回数据}).then(function(data){ console.log(data);});
那么输入就变成了这样:
异步1实现xxx1异步2实现xxx2间接返回数据
reject的用法
咱们对Promise有了最根本的理解。那么ES6的Promise还有哪些性能。咱们只用了resolve,还没用reject。咱们后面的例子只有“执行胜利”的回调,还没有“失败”的状况,reject的作用就是把Promise的状态置为rejected,这样咱们在then中就能捕捉到,而后执行“失败”状况的回调。不多说看代码。
function getNumber(){ var promise = new Promise(function(resolve, reject){ //异步操作 setTimeout(function(){ var num = Math.ceil(Math.random()*10); //生成1-10的随机数 if(num<=5){ resolve(num); } else{ reject('数字大了'); } }, 2000); }); return promise; } getNumber().then( function(data){ console.log('resolved'); console.log(data); }, function(reason, data){ console.log('rejected'); console.log(reason); });
getNumber函数用来异步获取一个数字,2秒后执行实现,如果数字小于等于5,咱们认为是“胜利”了,调用resolve批改Promise的状态。否则认为是“失败”了,调用reject并传递一个参数,作为失败的起因。
运行getNumber并且在then中传了两个参数,then办法能够承受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以咱们可能别离拿到他们传过来的数据。屡次运行这段代码,你会随机失去上面两种后果:
resolved1 或者rejected数字大了
catch的用法
Promise对象除了then办法,还有一个catch办法,它和then的第二个参数一样,用来指定reject的回调,用法是这样:
getNumber().then(function(data){ console.log('resolved'); console.log(data);}).catch(function(reason){ console.log('rejected'); console.log(reason);});
成果和写在then的第二个参数外面一样。不过它还有另外一个作用:在执行resolve的回调(也就是下面then中的第一个参数)时,如果抛出异样了(代码出错了),那么并不会报错卡死js,而是会进到这个catch办法中。请看上面的代码:
getNumber().then(function(data){ console.log('resolved'); console.log(data); console.log(somedata); //此处的somedata未定义}).catch(function(reason){ console.log('rejected'); console.log(reason);});
在resolve的回调中,咱们console.log(somedata);而somedata这个变量是没有被定义的。如果咱们不必Promise,代码运行到这里就间接在控制台报错了,不往下运行了。然而在这里,会失去这样的后果:
resolved4rejectedReferenceError: somedata is not defined(...)
也就是说进到catch办法外面去了,而且把谬误起因传到了reason参数中。即使是有谬误的代码也不会报错了,这与咱们的try/catch语句有雷同的性能。
all的用法
Promise的all办法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。咱们仍旧应用下面定义好的runAsync1、runAsync2、runAsync3这三个函数,看上面的例子:
Promise.all([runAsync1(), runAsync2(), runAsync3()]).then(function(results){ console.log(results);});
用Promise.all来执行,all接管一个数组参数,外面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then外面。那么,三个异步操作返回的数据哪里去了呢?都在then外面呢,all会把所有异步操作的后果放进一个数组中传给then,就是下面的results。所以下面代码的输入后果就是:
异步1实现异步2实现异步3实现[“xxx1”,”xxx2”,”xxx3”]
有了all,你就能够并行执行多个异步操作,并且在一个回调中解决所有的返回数据。关上网页时,事后加载须要用到的各种资源如图片、flash以及各种动态文件。所有的都加载完后,咱们再进行页面的初始化。
race的用法
all办法的成果实际上是“谁跑的慢,以谁为准执行回调”,那么绝对的就有另一个办法“谁跑的快,以谁为准执行回调”,这就是race办法,这个词原本就是赛跑的意思。race的用法与all一样,咱们把下面runAsync1的延时改为1秒来看一下:
Promise.race([runAsync1(), runAsync2(), runAsync3()]).then(function(results){ console.log(results);});
这三个异步操作同样是并行执行的。后果你应该能够猜到,1秒后runAsync1曾经执行完了,此时then外面的就执行了。后果是这样的:
异步1实现xxx1异步2实现异步3实现
在then外面的回调开始执行时,runAsync2()和runAsync3()并没有进行,仍旧再执行。于是再过1秒后,输入了他们完结的标记。
咱们能够用race给某个异步申请设置超时工夫,并且在超时后执行相应的操作,代码如下:
//申请某个图片资源function requestImg(){ var promise = new Promise(function(resolve, reject){ var img = new Image(); img.onload = function(){ resolve(img); } img.src = 'xxx'; }); return promise;} //延时函数,用于给申请计时function timeout(){ var promise = new Promise(function(resolve, reject){ setTimeout(function(){ reject('图片申请超时'); }, 5000); }); return promise;} Promise.race([requestImg(), timeout()]).then(function(results){ console.log(results);}).catch(function(reason){ console.log(reason);});
requestImg函数会异步申请一张图片,我把地址写为"xxx",所以必定是无奈胜利申请到的。timeout函数是一个延时5秒的异步操作。咱们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片申请胜利了,那么便进入then办法,执行失常的流程。如果5秒钟图片还未胜利返回,那么timeout就跑赢了,则进入catch,报出“图片申请超时”的信息。运行后果如下:
GET file:///D:/nodeJS/xxx net::ERR_FILE_NOT_FOUND
图片申请超时