Posts List

[译]什么是Promise.try

原文地址 http://cryto.net/~joepie91/blog/2016/05/11/what-is-promise-try-and-why-does-it-matter/ 在#Node.js#频道里经常困扰大家的一个话题是Bluebird提供的Promise.try方法。大家并不清楚该方法的功能也不知道为何要使用它。同时,几乎所有的关于Promsie的指南中针对该方法错误的演示使得这种情况没有任何改善。 在本文中,我会尝试解释究竟什么是Promise.try以及为何你应该使用它。我假设你已经对Promise有所了解并且知道.then在Promise中的作用。 即使你在使用一个不同的Promsie实现(例如ES6 Promise),本文还是可以帮到你。文章末尾我会解释如何在非Bluebird环境中实现相同的功能。 究竟什么是Promise.try呢? 简单来说,除了不需要跟在一个前置Promise之后以外,Promise.try很像.then。这么说还是有一些含糊不清,所以让我们先看一个示例。 以下是一段典型的Promise使用场景: function getUsername(userId) { return database.users.get({id: userId}) .then(function(user) { return user.name; }); } 到目前为止,一切顺利。我们假设database.users.get会返回一个Promise,并且该Promise最终会返回一个带有name属性的对象。 以下是同样的代码,但是引入了Promise.try: var Promise = require("bluebird"); function getUsername(userId) { return Promise.try(function() { return database.users.get({id: userID}); }).then(function(user) { return user.name; }); } 可以看到,我们的调用链以Promise.try而不是database.users.get开始。像使用.then一样,我们执行Promise.try方法并传递给它一个直接返回database.users.get调用的函数。 这样做有什么意义呢? 以上的代码看起来似乎是多余的。但实际上它有以下几个优点: 更好的错误处理 同步代码中的异常不论出现在何处都会以rejection的形式向Promise链后端传递。 更好的兼容性 你可以始终使用你自己喜欢的Promise实现,而不用担心第三方代码在使用哪个。 更好的代码阅读体验 所有的代码在水平方向上将处于同一个缩进层级,这将使你阅读代码变得更容易。 接下来我会逐一介绍这些优点: 1. 更好的错误处理 Promise的一个被大力宣扬的优点就是用户可以用同一种方式同时处理同步异常和异步异常 —— 同步异常会被捕获并且会作为一个rejected Promise向后传递。但事实真的是这样吗?让我们看看以下这个上文示例的小变种: function getUsername(userId) { return database.users.get({id: userId}) .then(function(user) { return uesr.

koa框架源码阅读笔记 之 lib/application.js

最近在学习ES6 Generator特性时发现了koa这个基于Generator的Web框架,它可以让开发者以一种“同步的方式”编写包含各种异步请求的Web应用。下面是关于它的一段中文介绍: 由 Express 原班人马打造的 koa,致力于成为一个更小、更健壮、更富有表现力的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。 从介绍中可以看出这又是一个小而美的框架。到GitHub项目页面上看了一下发现源码里只有4个JS文件,总代码量只有1571行(以2015年12月26日最新稳定版1.1.2为准)。于是决定把代码clone下来学习一下,本篇会先从整个框架的入口文件lib/application.js说起。 总体说明 lib/application.js文件export出的是一个构造函数,用来创建一个koa应用。一个koa应用最常用的方法有2个: - listen(port) 执行listen后会通过http.createServer启动一个服务器并监听指定端口 - use(middleware) 注册一个中间件,一个koa应用可以注册多个中间件, 处理请求时会按照中间件注册的顺序执行这些中间件。 更多详细信息可以参考GitHub文档页面。 代码注释 function Application() { if (!(this instanceof Application)) return new Application; this.env = process.env.NODE_ENV || 'development'; this.subdomainOffset = 2; this.middleware = []; this.proxy = false; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } 以上就是Application构造函数的定义,很简洁,主要做了以下几件事情: - 通过instanceof判断来支持不带new关键字的调用。 - 设置应用运行环境,会从环境变量NODE_ENV读取,默认值为development - 声明this.

在JDF项目中使用ES6新特性

Note: 目前虽然JDF已经支持使用ES6开发脚本,但线上使用还在前期摸索阶段,请大家根据自己项目实际情况评估使用情况 最近利用业余时间给JDF增加了ES6代码的支持。背后的原理是在项目构建阶段利用Babel将.babel文件转译成ES5代码。关于Babel的更多使用方法可以参考其官网的配置文档。 下面开始正式介绍在利用ES6特性开发前的准备工作: - 升级JDF到最新版本(>= 1.8.2) - 进入JDF项目目录,安装基本的Babel preset和plugin。关于preset和plugin的更多说明请参考文档 npm install babel-preset-es2015 npm install babel-plugin-transform-es3-member-expression-literals npm install babel-plugin-transform-es3-property-literals 配置项目的.gitignore文件,忽略node_modules目录。在文件中添加以下内容: **/node_modules/ 引入es6-base.js(包含 es5-shim和babel-polyfill) 至此,所有的准备工作就完成了。接下来就可以利用ES6的各种新特性开发了。需要注意的是所有包含了ES6特性的脚本文件扩展名必须是.babel,否则JDF是不会对其进行编译的。 最后是一份简单的FAQ: Q: ES6都有哪些新特性,有没有推荐的学习资料? 当然有,中文版的有阮一峰出品的ECMAScript 6入门。 英文的有: - ES6 In Depth来自Mozilla团队博客的一系列文章,每篇讲解一个新特性。建议按时间顺序阅读。 - Understanding ECMAScript 6来自Nicholas C. Zakas大神的一本ES6小书。 Q: 我想使用额外的Babel plugin或preset该怎样配置? 只需在package.json中指定需要额外引入的plugin或preset,例如: "babel": { "plugins": ["syntax-async-functions","transform-regenerator"], "presets": ["stage-0"] } 并在JDF项目的目录中安装相关npm package即可。 Q: .babel文件编译出来的代码我看不懂,出了问题如何调试呢? 我们看到的经过Babel转译后的JS是这样的: 这里包含了大量的Babel生成的代码,非常不利于调试。不过不用担心,JDF在本地开发模式中启用了sourceMap,你只需在Chrome开发者工具中的Sources Tab中在你要调试的JS文件目录下找到同名的.babel文件即可。相关JS中的报错信息,都会被映射到转译前的.babel文件的对应位置。 关于sourceMap的更多信息请参考这里。

[译] TrimPath模板引擎语法

本文翻译自JavaScript Template Syntax 表达式与表达式修饰符 ${expr} ${expr|modifier} ${expr|modifier1|modifier2|...|modifierN} ${expr|modifier1:argExpr1_1} ${expr|modifier1:argExpr1_1,argExpr1_2,...,argExpr1_N} ${expr|modifier1:argExpr1_1,argExpr1_2|...|modifierN:argExprN_1,argExprN_2,...,argExprN_M} expr可以是不包含右花括号}的任何合法JavaScript表达式 修饰符的格式是modifierName[:argExpr1[,argExpr2[,argExprN]]] argExpr可以是任何合法的expr Examples: ${customer.firstName} ${customer.firstName|capitalize} ${customer.firstName|default:"no name"|capitalize} ${article.getCreationDate()|default:new Date()|toCalenderControl:"YYYY.MM.DD",true,"Creation Date"} ${(lastQuarter.calcRevenue() - fixedCosts) / 1000000} 这里有一份内置的修饰符列表,你也可以通过TrimPath提供的API来创建自定义的修饰符。 表达式还可以写成${% customer.firstName %}这种形式,多出的%字符允许你的表达式中出现花括号}。例如: Visit our ${% emitLink('Solutions and Products', { color: 'red', blink: false }) %} 表达式中的空白字符是可选的,你也可以写成下面的格式: ${%customer.firstName%} ${%customer.firstName|capitalize%} 声明 声明标签可以嵌套使用。 控制流 {if testExpr} {elseif testExpr} {else} {/if} testExpr可以是任何不包含}的JavaScript表达式 testExpr不需要被(和)包围 示例:

AngularJS中的依赖注入

本文译自https://docs.angularjs.org/guide/di 依赖注入 依赖注入(DI)是一种解决组件如何获取其依赖这一问题的设计模式。 Angular injector子系统负责创建组件,解决它们的依赖,并按要求将它们提供给其他组件。 如果想深入了解DI,可以查看依赖注入的Wikipedia以及Martin Fowler的这篇Inversion of Control。 DI in a Nutshell 一个组件要想获取它的依赖可以通过以下三种途径: 直接创建这个依赖的一个实例,通常使用new操作符 通过全局变量查找已经被创建的依赖 依赖可以作为参数传递给需要它的组件 前两种获取依赖的方式并不理想,因为这会导致依赖关系被硬编码进代码中。这会导致更新依赖关系变得很复杂。在测试时尤其如此,我们通常会提供mock过的依赖关系以隔离不同组件间的测试,硬编码会导致每次测试都需要修改相关的源码。 相比之下,第三种方式是最可行的,因为它将解决依赖关系的责任从组件中移除。依赖只是简单的作为参数传递给组件: function SomeClass(greeter) { this.greeter = greeter; } SomeClass.prototype.doSomething = function(name) { this.greeter.greet(name); } 在上边的例子中,SomeClass并不关心如何创建或找到greeter,它只是在初始化时从参数列表里取出需要的依赖即可。 但这样做的问题是,寻找/创建依赖的责任被转交给了调用SomeClass的代码。 为了统一管理依赖关系,每个Angular应用都有一个injector。injector是一个服务定位器,它负责查找或创建依赖。 下面是一个使用injector服务的例子: // Provide the wiring information in a module var myModule = angular.module('myModule', []); 下面的代码告诉injector如何创建greeter服务。需要注意的是greeter又依赖于$window服务。greeter实际上是一个包含greet方法的对象。 myModule.factory('greeter', function($window) { return { greet: function(text) { $window.alert(text); } }; }); 下面的代码演示了如何创建一个injector并通过它来请求greeter服务:

AngularJS源码阅读之ngModel

数据的双向绑定可能是Angular最为人们熟知的特性之一。举个最简单的例子 可以在result页面中看到,每当在input中输入时,$scope中对应model的值也改变了。反之,当用户点击Set按钮在$scope中更新了model的值时,input输入框中的内容也对应更新了。 所有的这些魔法只需要我们在input元素上指定一个ng-model属性。可见ng-model这个directive是双向绑定这一特性不可缺少一点。本文就继续从源代码入手看看ngModel的实现方式以及其它directive是如何与ngModel交互的。 ngModel被定义在input.js。从名字上可看出,该文件还定义input这个directive,从这点也可以看出ngModel与input之间的紧密关系。 注: 文中所有涉及到的AngularJS源码均来自angular-1.2.8版本。 var ngModelDirective = function() { return { require: ['ngModel', '^?form'], controller: NgModelController, link: function(scope, element, attr, ctrls) { // notify others, especially parent forms var modelCtrl = ctrls[0], formCtrl = ctrls[1] || nullFormCtrl; formCtrl.$addControl(modelCtrl); scope.$on('$destroy', function() { formCtrl.$removeControl(modelCtrl); }); } }; }; 可以看出ngModel依赖于ngModelController以及可选的formController。ngModel在link方法中只做了2件事: 如果声明了ngModel的元素出现在一个form中,那么就向上层form注册自身 注册了$destroy事件的监听器,该事件触发时告知上层form移除对自身的引用 link方法如此简单以至于我们还没看到关于数据绑定的任何信息。看来问题的答案都藏在ngModelController中了。接下来看看ngModelController中都发生了什么。 var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', function($scope, $exceptionHandler, $attr, $element, $parse) { this.

Angular中的Controller

在Angular中,一个Controller是一个JavaScript构造函数。当一个Controller通过ng-controller附加到某个DOM元素上时,Angular会使用相应Controller的构造函数来初始化一个新的Controller对象。一个新的child scope会被创建并作为可注入的参数(就是我们看到的$scope)传递给该Controller的构造函数。 使用Controller: 为$scope对象设置初始状态 为$scope对象添加行为 不要使用Controller: 修改DOM —— Controllers应该只负责业务逻辑。将任何UI的逻辑放进Controller都会极大地破坏其可测试性。Angular中可以使用数据绑定用来更新页面中的数据或使用directive来封装DOM操作。 格式化input数据 —— 使用angular form controls 过滤输出数据 —— 使用Angular中的filter机制 跨Controller之间的代码或状态共享 —— 使用Angular中的service机制 管理其他组件的生命周期

AngularJS源码阅读之ngRepeat

注: 文中所有涉及到的AngularJS源码均来自angular-1.2.8版本。 注2: 本文结构为一段代码配一段说明,代码在上,说明在下。 从3月3号到现在,进入新公司也有1个月了。过去这一个月里主要负责公司内部某个DevOps工具开发过程中的前端部分。由于涉及到较多的网页交互,该项目的前端部分使用了AngularJS这个框架。本人这正好借助这个机会进一步了解了AngularJS的相关知识。 这两天项目主体完成,准备上线,算是有了一些自由时间,正好借此机会学习一下AngularJS内部的实现机制。 本篇来说说最常见的directive之一 ngRepeat 该文件的整体结构如下: var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { var NG_REMOVED = '$$NG_REMOVED'; var ngRepeatMinErr = minErr('ngRepeat'); return { transclude: 'element', priority: 1000, terminal: true, $$tlb: true, link: function($scope, $element, $attr, ctrl, $transclude){ ..... } }; function getBlockStart(block) { return block.clone[0]; } function getBlockEnd(block) { return block.clone[block.clone.length - 1]; } }]; ngRepeatDirective是一个数组,它会被传递给directive方法用来生成ngRepeat这个directive。可以看到ngRepeat依赖于$parse和$animate两个服务。$parse用来将字符串解析成javascript函数。$animate用来给DOM改变附加上动画效果。文件最后声明了两个helper方法,会在稍后的分析中介绍到。 接下来把最大的篇幅交给这个directive的核心——link方法。 var expression = $attr.ngRepeat; var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, lhs, rhs, valueIdentifier, keyIdentifier, hashFnLocals = {$id: hashKey}; if (!

AngularJS Directive之compile与link

刚开始学习 AngularJS Directive 时,总是不太能搞明白 compile 和 link 两个属性的含义和区别。最近写的多一些,读的资料也多了一些,渐渐有了一些理解,简单记录下来: compile 属性是一个函数。该函数会针对每一个 directive 的实例执行一次,在 compile 阶段: 不能访问 scope 元素还没有被插入 document 可以修改 template,最终结果会被 angular 缓存 link 属性可以有两种配置方式。 link 属性可以被设置为一个对象,包含 pre, post 两个属性,分别对应一个函数(preLink, postLink)。preList,postLink 的区别在于执行顺序。preLink 是自顶向下的,先父节点,后子节点,postLink 正好相反。 .directive{ return { link: { pre: function preLink(){ ... }, post: function postLink() { ... } } } } link 属性也可以被直接设置为一个函数,则默认相当于设置了 postLink 函数。 .directive{ return { link: function link(){ ... } } } 等于