我最近正在看的一本书《聊聊架构》,在进入今天的主题以前,我想和你们分享这本书里的一个概念“生命周期”。javascript
大体是这么说的:html
人类的生命很短,百年也只有短短的三万六千天。大部分人都不肯意接受一切都将消逝的事实,总想活得更久,占有更多,享受更多。在人们短短的一辈子中,如何延长自身的生命呢?一个办法就是尽量作出更多的成就,可以让更多的人生活得更好。在一样的时间内创造出更多的产出,至关于把本身的生命延长了。前端
其中有效的作法就是将每次活动进行拆分,本身执行核心的【生命周期】,将【非核心生命周期】交给其它主体进行。java
好比,用户购物这一场景,从用户进入到商店,进行浏览、询问、购买等活动,到离开商店,都是按时间顺序一步一步在执行的。对于咱们来讲,这其中的每一步都须要时间,对于现代人来讲,太奢侈了。若是这时候,咱们把这个购物行为进行拆分,把选购交由别人执行,由别人代替用户上街选择和过滤,或者经过网上推荐等来完成选购,用户只须要最后肯定物品的挑选便可。用户的目的是买到本身须要的东西,而不是选购自己。这样就能够大大节省用户的时间,将更多的精力放到其余核心事情上。数据库
再好比,如今吃饭,须要本身到菜市场买菜、回来煮饭煮菜,吃完饭后,还须要本身洗碗等。有时候彻底能够叫外卖的,肚子饿了,点点【饿了吗】,接着就能够继续作本身的事情了,坐等外卖送到。将【煮饭】这一耗时的事情交给别人来作。编程
在编程语言中,这就是同步与异步的区别。异步的做用就是将耗时的事情交给【别人】来作,本身继续进行;当【别人】作完事情后,执行回调函数,带回结果,交回本身执行。数组
虽然 Javascipt 语言是“单线程”执行环境,但在执行模式下,分红同步和异步两种模式,其中咱们更多的使用回调函数的方式来进行异步操做,如:promise
blogs.search = (words, res) => { const titleQuery = new AV.Query(Blog) titleQuery.contains('title', words); const descQuery = new AV.Query(Blog) descQuery.contains('desc', words); const tagsQuery = new AV.Query(Blog) tagsQuery.contains('tags', words); const wordsQuery = AV.Query.or(titleQuery, descQuery, tagsQuery); wordsQuery.descending('createdAt'); wordsQuery.limit(5); wordsQuery.find().then(function (results) { res(results); }, function (error) { res([]); }); }
这函数的做用就是经过关键词words
搜索,获取知足条件的公众号文章,若是找不到就返回空数组。其中这里的res
就是一个回调函数。在使用处:架构
server.route({ method: 'POST', path: '/searchblog', handler: function (request, reply) { const words = request.payload.words; ModelBlog.search(words, results => { let data = []; results.forEach(function(v) { let wrap = {}; wrap.title = v.get('title'); wrap.description = v.get('desc'); wrap.picurl = v.get('picurl'); wrap.url = v.get('url'); data.push(wrap); }); reply(data); }); }, config: { validate: { payload: { words: Joi.string().required() } } } });
一样的,在handler
函数中的reply
也是一个回调函数,先经过ModelBlog.search
函数内嵌回调函数获取数据库中的文章数组,而后再对回调结果进行处理,返回给回调函数reply
,最后返回给前端使用。curl
经过一个简单的例子——“回调函数,内嵌回调函数”来讲明回调函数是能够无穷尽的内嵌,结果就是各个部分之间高度耦合,流程混在一块儿,每一个任务只能指定一个回调函数。最终形成的结果就会嵌入回调地狱,极可能就像这样了——结尾是无止境的});
:
图片来自于:
https://tutorialzine.com/media/2017/07/callback-hell.jpg
所谓 Promise, 就是一个对象,用来传递异步操做的消息。它表明了某个将来才会知道结果的事件 (一般是一个异步操做),而且这个事件提供统一的 API,可供进一步处理。
————来自《ES 6标准入门 (第二版)》
基于回调函数的异步处理若是统一参数使用规则的话,写法也会很明了。可是,这也仅是编码规范而已,即便采用不一样的写法也不会出错。
而 Promise 则是把相似的异步处理对象和处理规则进行规范化,并按照采用统一的接口来编写,而采用规定方法以外的写法都会出错。
除了 Promise 对象规定的方法 (这里的 then 或 catch )之外的方法都是不可使用的,而不会像回调函数方式那样能够本身自由的定义回调函数的参数,而必须严格遵照固定、统一的编码方式来编写代码。
这样,基于 Promise 的统一接口的作法,就能够造成基于接口的各类各样的异步处理模式。因此,Promise 的功能是能够将复杂的异步处理轻松的进行模式化,这也能够说是使用 Promise 的理由之一。
Promise 在规范上规定 Promise 只能使用异步调用方式。
若是将上面的 demo 从新用 Promise 来写呢:
blogs.promise_search = (words) => { const promise = new Promise(function (resolve, reject) { const titleQuery = new AV.Query(Blog) titleQuery.contains('title', words); const descQuery = new AV.Query(Blog) descQuery.contains('desc', words); const tagsQuery = new AV.Query(Blog) tagsQuery.contains('tags', words); const wordsQuery = AV.Query.or(titleQuery, descQuery, tagsQuery); wordsQuery.descending('createdAt'); wordsQuery.limit(5); wordsQuery.find().then(function (results) { resolve(results); }, function (error) { reject(error); }); }); return promise; }
Promise 构造函数接受一个函数做为参数,该函数的两个参数分别是 resolve 和 reject。它们是两个函数,由 Javascript 引擎提供,不用本身传入。
其中,resolve 函数的做用是,将 Promise 对象的状态从“未完成”变成“成功” (即从 Pending 变为 Resolved),在异步操做成功时调用,并将异步操做的结果做为参数传递出去;
reject 函数的做用是,将 Promise 对象的状态从“未完成”变为“失败” (即从 Pending 变为 Rejected),在异步操做失败时调用,并将异步操做报出的错误做为参数传递出去。
Promise 实例执行之后,能够用then
方法分别指定Resolved
状态和Rejected
状态的回调函数。因此在使用处:
const words = request.payload.words; ModelBlog.promise_search(words).then(function (results) { let data = []; results.forEach(function(v) { let wrap = {}; wrap.title = v.get('title'); wrap.description = v.get('desc'); wrap.picurl = v.get('picurl'); wrap.url = v.get('url'); data.push(wrap); }); reply(data); }).catch(function (error) { reply([]); });
其中,then
函数接受回调函数,做为 Promise 对象的状态变为 Resolved 时调用;而 catch 回调函数做为 Promise 对象的状态变为 Rejected 时调用。和【异步函数】相比,简单明了不少了,至少不用再传递回调函数到 ModelBlog 中,能够作到代码的分离,ModelBlog 的做用只是为了拿到数据,返回 Promise 对象,具体外界怎么使用,那是别人的事情了;一样在使用方,能够直接调用 Promise 对象,经过 then 方法处理回调数据和错误信息,代码也就更容易理解了。
但写代码总不能处处都是 Promise 对象,既然 Promise 能解决异步调用地狱的问题,但还有没有更好的办法将 Promise 异步方法写的和同步写法那样,毕竟不少人已经习惯面向过程的编写方式了?
Async/Await是一个好久就使人期待的 JavaScript 功能,它让使用异步函数更加愉快和容易理解。它是基于 Promise 的而且和现存的全部基于 Promise 的 API 相兼容。
从 async 和 await 这两个名字来的这两个关键字将会帮助咱们整理咱们的异步代码。
async function getBlogsAsync(words) { const titleQuery = new AV.Query(Blog) titleQuery.contains('title', words); const descQuery = new AV.Query(Blog) descQuery.contains('desc', words); const tagsQuery = new AV.Query(Blog) tagsQuery.contains('tags', words); const wordsQuery = AV.Query.or(titleQuery, descQuery, tagsQuery); wordsQuery.descending('createdAt'); wordsQuery.limit(5); let results = await wordsQuery.find(); return results; }
这下连new Promise(...)
都省了,直接写核心业务代码。很明显 Async/Await 版本的代码更短而且可读性更强。除了使用的语法,两个函数彻底相同——他们都返回 Promise 而且都从数据库获得 Blogs 数据返回。在使用时,仍是和以前同样,直接调用getBlogsAsync
方法:
getBlogsAsync(words).then(function (results) { let data = []; results.forEach(function(v) { let wrap = {}; wrap.title = v.get('title'); wrap.description = v.get('desc'); wrap.picurl = v.get('picurl'); wrap.url = v.get('url'); data.push(wrap); }); reply(data); }).catch(function (error) { reply([]); });
随着 Async/Await ,JavaScript语言在代码可读性和易用性上向前迈进了一大步。并且写异步代码,就跟常规的写面向过程的同步代码同样,简单直接明了。
最后分享几个相关资料,值得一看,还有更多深刻的内容须要继续挖掘:
Javascript异步编程的4种方法。http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
《ES 6标准入门 (第二版)》,做者:阮一峰
JavaScript Async/Await Explained in 10 Minutes. https://tutorialzine.com/2017/07/javascript-async-await-explained
八段代码完全掌握 Promise. http://www.javashuo.com/article/p-mkzxierw-gk.html
JavaScript Promise迷你书(中文版) http://liubin.org/promises-book/#promises-overview
理解 async/await. http://www.javashuo.com/article/p-uhsbtafj-h.html
据说最美的人和最帅的人,都会给做者打赏,以资鼓励
coding01 期待您关注