李晨亮的博客

IT技术男,恋技术,爱运动

0%

这是我的 Promises 规范学习笔记,用自己能理解的方式描述 Promises 规范的内容。

Promises/A+

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.

A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.

This specification details the behavior of the then method, providing an interoperable base which all Promises/A+ conformant promise implementations can be depended on to provide. As such, the specification should be considered very stable. Although the Promises/A+ organization may occasionally revise this specification with minor backward-compatible changes to address newly-discovered corner cases, we will integrate large or backward-incompatible changes only after careful consideration, discussion, and testing.

Historically, Promises/A+ clarifies the behavioral clauses of the earlier Promises/A proposal, extending it to cover de facto behaviors and omitting parts that are underspecified or problematic.

Finally, the core Promises/A+ specification does not deal with how to create, fulfill, or reject promises, choosing instead to focus on providing an interoperable then method. Future work in companion specifications may touch on these subjects.

Terminology 术语

  1. “promise”是具有 then 方法的对象或函数,其行为符合此规范。
  2. “thenable”是一个定义 then 方法的对象或函数。
  3. “value”是任何合法的 JavaScript 值(包括 undefined、thenable 或 promise)。
  4. “exception”是一个使用 throw 语句抛出的值。
  5. “reason”是一个值,它说明了一个 promise 为什么被拒绝。

Requirements 要求

Promise 的状态

promise 必须是三种状态中一种:请求态(pending),完成态(fulfilled),拒绝态(rejected)

  1. promise 是 pending 状态时:
    1. 可转换为 fulfilled 或 rejected 状态。
  2. promise 是 fulfilled 状态时:
    1. 不能转换为任何其他状态。
    2. 必须有个值,此值不能改变。
  3. promise 是 rejected 状态时:
    1. 不能转换为任何其他状态。
    2. 必须有个值,此值不能改变。

在这里,“此值不能改变”意味着不变的身份(即===),但并不意味着深层的不变性。

这句话还不能理解透是什么意思

then 方法

promise 必须提供一个 then 方法来访问它当前/最终的值或 reason。
promise’s then 方法有两个参数:

1
promise.then(onFulfilled, onRejected)
  1. onFulfilledonRejected 都是可选参数:

    1. 如果 onFulfilled 不是函数,必须忽略。
    2. 如果 onFulfilled 不是函数,必须忽略。
  2. 如果 onFulfilled 是函数:

    1. 它必须在 promise 为 fulfilled 后调用,并把 promise 的值作为它的第一个参数。
    2. 它绝对不能在 promise 为 fulfilled 之前调用。
    3. 它不能被调用超过一次。
  3. 如果 onRejected 是函数,

    1. 它必须在 promise 为 rejected 后调用,并把 promise 的 reason 作为它的第一个参数。
    2. 它绝对不能在 promise 为 rejected 之前调用。
    3. 它不能被调用超过一次。
  4. onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

  5. onFulfilledonRejected 必须作为函数调用 (i.e. with no this value). [3.2]

  6. then可以被同一个 promise 调用多次。

    1. 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
    2. 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
  7. then 必须返回一个 promise 对象 [3.3]。

    1
    promise2 = promise1.then(onFulfilled, onRejected);
    1. 如果 onFulfilledonRejected 返回一个值 x,run the Promise Resolution Procedure [[Resolve]](promise2, x).
    2. 如果 onFulfilledonRejected 抛出异常 e, promise2 必须拒绝执行,并返回 reason e
    3. 如果 onFulfilled 不是一个函数并且 promise1 是 fulfilled, promise2 必须为 fulfilled 并且返回与 promise1 相同的 value
    4. 如果 onRejected 不是一个函数并且 promise1 是 rejected, promise2 必须为 rejected 并返回与 promise1 相同的 rejected

Promise 解决过程

promise resolution procedure 是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise

这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

运行 [[Resolve]](promise, x),须遵循一下步骤:

  1. 如果 promisex 是同一个对象,并以 TypeErrorpromise 的 reason
  2. 如果 x 是个 promise,则 promise 接受 x 的状态 [3.4]:
    1. 如果 x 是 pending, promise 必须保持状态为 pending ,直到 x 为 fulfilled 或 rejected
    2. 如果 x 处于 fulfilled, promise 的 fulfill 使用同样的 value
    3. 如果 x 处于 rejected, reject promise 的 reject 使用相同的 reason.
  3. 另外,如果 x 是对象或函数
    1. x.then 赋给 then . [3.5]
    2. 如果取 x.then 的值时抛出错误 e ,则以 e 为 reason 拒绝 promise
    3. 如果 then 是个函数, 将 x 作为函数作用于的 this,第一个参数 resolvePromise, and 第二个参数 rejectPromise, where:
      1. If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
      2. 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
      3. 如果 rejectPromise 以 reason r 为参数被调用,则以reason r 拒绝 promise
      4. 如果 resolvePromiserejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
      5. 如果调用 then 抛出了异常 e
        1. 如果 resolvePromiserejectPromise 已经被调用,则忽略它
        2. 否则,以 e 作为 reject promise 的 reason
    4. 如果 then 不是函数,以x为参数执行 promise
  4. 如果 x 不是对象或函数,以x为参数执行 promise

如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise 。 [3.6]

Notes

  1. Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.

  2. That is, in strict mode this will be undefined inside of them; in sloppy mode, it will be the global object.

  3. Implementations may allow promise2 === promise1, provided the implementation meets all requirements. Each implementation should document whether it can produce promise2 === promise1 and under what conditions.

  4. Generally, it will only be known that x is a true promise if it comes from the current implementation. This clause allows the use of implementation-specific means to adopt the state of known-conformant promises.

  5. This procedure of first storing a reference to x.then, then testing that reference, and then calling that reference, avoids multiple accesses to the x.then property. Such precautions are important for ensuring consistency in the face of an accessor property, whose value could change between retrievals.

  6. Implementations should not set arbitrary limits on the depth of thenable chains, and assume that beyond that arbitrary limit the recursion will be infinite. Only true cycles should lead to a TypeError; if an infinite chain of distinct thenables is encountered, recursing forever is the correct behavior.

参考资料

https://promisesaplus.com/
http://www.ituring.com.cn/article/66566
https://segmentfault.com/a/1190000015914967

时间:2019 年 3 月 24 日 星期日

天气:-5 ~ 9℃ 多云 西南风 4-5 级 空气质量 53 良

今天是 2019 高校跑的第三站东北林业大学。距离上次高校跑已经过去了三个星期。大上个星期日去工大医院看病了,上个星期在家弄工作的东西。这个星期可算是跑上了,不然就隔了一个月,如果按这个速度,19 年连一表都跑不完。

早上六点多点到达第 9 门附近开始今天的高校跑。

为了城市建设,哈尔滨有不少的高校被公路、立交桥或是隧道分为两部分,林大就是其中之一。

阅读全文 »

MyBatis-Plus 在 SpringBoot 的中有个配置项 field-strategy 。看官方文档并没有看懂是什么意思,官方说明如下:

该策略约定了如何产出注入的sql,涉及insert,update以及wrapper内部的entity属性生成的 where 条件

作为一个好刨根问底的技术男,各种搜索后未找到答案。探索未知是我的使命,那么向源码出发!

其实,要找到某个配置项都用在什么地方,还是挺不容易的。首先,得找到配置文件的处理类,在这个处理类获得了配置的数据后,又放到了哪里。又有什么程序去读取了这个配置数据,读取了后又是怎么使用的。找到使用的地方,就基本算是找到根了。可以看看不同配置值,分别是如何处理的。

根据配置的名词,去寻找对应的类。

1
2
3
4
5
6
# 忽略判断
field-strategy: 0
# 非 NULL 判断
field-strategy: 1
# 非空判断
field-strategy: 2
1
2
3
mybatis-plus:
global-config:
field-strategy:

FieldStrategy

在包里找到了这个枚举 com.baomidou.mybatisplus.enums.FieldStrategy,后面查找这个枚举的引用就很容易找到用这些配置值的地方了。

1
2
3
4
5
public enum FieldStrategy {
IGNORED(0, "忽略判断"),
NOT_NULL(1, "非 NULL 判断"),
NOT_EMPTY(2, "非空判断");
}

NOT_EMPTY

我来看看源码里到底是什么意思,源码如下:

1
2
3
4
5
6
7
if (fieldStrategy == FieldStrategy.NOT_EMPTY) {
return StringUtils.isCharSequence(propertyType) ?
String.format("\n\t<if test=\"%s!=null and %s!=''\">", property, property) :
String.format("\n\t<if test=\"%s!=null \">", property);
} else {
return String.format("\n\t<if test=\"%s!=null\">", property);
}

结论是 NOT_EMPTY 的意义为 <if test="%s!=null and %s!=''"> ,即:!= null 并且 != ‘’

StringUtils.isCharSequence(propertyType) 当前处理的字段类型是否为字符串类型

AutoSqlInjector

下面来看看在 com.baomidou.mybatisplus.mapper.AutoSqlInjector 中的使用:

1
2
3
4
5
6
7
8
9
10
11
// 验证逻辑
if (fieldStrategy == FieldStrategy.NOT_EMPTY) {
if (StringUtils.isCharSequence(propertyType)) {
return String.format("\n\t<if test=\"%s!=null and %s!=''\">", property, property);
} else {
return String.format("\n\t<if test=\"%s!=null \">", property);
}
} else {
// FieldStrategy.NOT_NULL
return String.format("\n\t<if test=\"%s!=null\">", property);
}

其实,和上面的那个是一个意思。但,看这两段代码有着不一样的写法,而且是也属于重复逻辑的多次书写,应该是可以优化为一个方法的。

先来引用一段 Hexo 中文文档中的一段话。

通过常规的 markdown 语法和相对路径来引用图片和其它资源可能会导致它们在存档页或者主页上显示不正确。在Hexo 2时代,社区创建了很多插件来解决这个问题。但是,随着Hexo 3 的发布,许多新的标签插件被加入到了核心代码中。这使得你可以更简单地在文章中引用你的资源。

1
2
3
{% asset_path slug %}
{% asset_img slug [title] %}
{% asset_link slug [title] %}

按照上面说的引用方式,文章中的图片无论是在首页还是文章详情页显示都不会有问题的。可是,在使用可视化 Markdown 编辑工具(如:Typora)的时候图片是显示不出来的,只能在 Hexo 的本地 Server 里才能看到效果。另外,如果想把 md 文件导出为PDF或是word文档,那不好意思你只能去把图的引用方式修改为标准的 Markdown 插入图片语法。

因为在不同的环境中都有各自的语法支持,如:{/% asset_img slug [title] %} 仅在 Hexo 中可解释。我自己想出了个办法,就是利用这种再不同环境中对语法支持不同,写段能自适应的图片引用方式,代码如下:

1
2
3
4
5
6
7
{% if 1 == 1 %}
{## 这个只在 Hexo 环境中有效 ##}
{% asset_img cannot-delete-selected-package.png [EA不能删除选择的包] %}
{% else %}
{## 这是原生的 Markdown 插入图片语法 ##}
![EA不能删除选择的包](./cannot-delete-selected-package/cannot-delete-selected-package.png)
{% endif %}

因为 Markdown 是不支持{/% %}这种标签的解析,所以在纯 Markdown 解析环境中会把上面代码中那些 Hexo 中有效的标签都忽略掉。那么就只剩下原生的 Markdown 插入图片语法了。

空模板

1
2
3
4
5
{% if 1 == 1 %} 
{% asset_img xx.png title %}
{% else %}
![](./xx.png 'title')
{% endif %}

终极大招

如果这样感觉还是很费劲,那么还有终极大招!!!

那就是用图床吧,使用http绝对路径为图片的地址。

推荐大家使用七牛的对象存储服务(七牛官网),本站使用的就是这个。

时间:2019年3月3日 星期日

天气:温度 -9°,微风,空气质量指数 247 中度污染

这是2019高校跑的第二站哈尔滨工程大学。

早上6点出发,大约半个小时后来到文庙(领票处)门口。戴好帽子、手套、耳机,往“Keep哈尔滨”微信里发个位置,出发!

进校园

做完跑前热身,从文庙门前的文庙街自西向东开始跑。记得这条路是可以进到校园内的。

途中经过黑龙江省军区门前,拍照的时候警觉的哨兵一直在盯着我,看的我好不自在。

可跑到头的时候傻眼了,有个栅栏门封住了去路。

这时体会到驴友到未知的领域,前不着村后不着店,会遇到多少困难,多么无助的感觉了。。
本想掉头往回跑,可就算跑回到出发的地方,这大早上的,也找不到问路的人,还是不知道怎么进校园。这个时候发现栅栏门的左侧旁有路口,不管了,往这里去看看能不能进去吧。左转、右转、往前到头,有个大铁门上写着“不通学校”,没办法有往右转,跑到头是墙,没有路了。原路返回,到刚才那个大铁门的时候,看见有两个大娘在从铁门旁边的一个很不起眼的小门那迈过个铁桩子。我也抱着碰碰运气的想法去过看看,过了这个门就豁然开朗了,真的能进去!

北体育场

过了小门就是校园内的东海路,顺着东海路自南向北第一个路口就是南海路,拐入南海路就可以看见北体育场了。哈工程的北体育场看着和哈工大的主体育场差不多。有看台有主席台。可和哈工大一样都是锁着门的,可能只有专业的体育生训练的时候才能进去吧。只能在北体育场外绕了一圈,有个门缝比较大,手伸进去拍了下面这张照片。

体育馆

对哈工程的体育馆印象还是很深刻的,因为第一次去室内篮球馆就是在这里。那还是在初中的时候,骑自行车从现在群力那的省五院到哈工程体院馆,大概有16公里多的路程。那个时候的体力真好。记得那时候有早场、上午场、下午场。我们经常去的是上午场,记得好像是5块钱3小时吧。那个时候经常出席的还真不多少,有些现在都记不起名字了,反正36中打篮球的都是这帮人。还有玻璃瓶装的大白梨,人多时候时候还抬一箱。

而现在正在改建修缮中。

篮球场4

体育馆门前就是篮球场了,不过这时候篮球场里面空空荡荡。

军工操场

篮球场的北面是军工操场(足球场),场地非常不错,已经有一队人在踢得火热了。

南体育场

南体育场是个一圈400米的标准田径场,跑道内还有足球和橄榄球的球门。

与南体育场南边一道相隔的是哈工程篮球场,这看似是塑胶地面的。可能是离寝室近吧,已经有不少人在打球了。

小遗憾

  • 进校园都费了不少劲儿
  • 在校园内确实有点迷路
  • 没有事先规划好线路
  • 拍照的时候解锁太烦了
  • 不知道正门在哪
  • 怎么没去足球场里面跑跑体验下草地呢
  • 忘记自拍

《Pentaho 8 Reporting for Java Developers》,是本介绍 Pentaho 报表开发的英文版书籍。

Pentaho 的资料和书籍比较少,有本英文的对付看看图,看看范例代码也是好的。

可在网上搜到的代码不见得就是可以运行起来的。

记录分享下,避免以后和别的小伙伴再采坑。

这个是好使的范例代码,可运行起来的。
https://github.com/fcorti/pentaho-8-reporting-for-java-developers

这个貌似是这本出版社的范例代码。但这个里面的代码是运行不起来的。
https://github.com/PacktPublishing/Pentaho-8-Reporting-for-Java-Developers

今天同事XJ推荐了个好东西。

这是个 Chrome 浏览器插件叫“谷歌访问助手”,用途是解决了在国内无法使用谷歌搜索的问题。有了这个插件就可以使用谷歌搜索了,对于搞技术的人来说,谷歌搜索的结果确实比某度好不少。

作为理工男,第一想法的是这插件是什么机制?因为之前略微研究过Chrome插件开发,所以对插件的源码结构还略知一二。看了下js代码,已经被压缩过了,大概看了下好像是控制了 Chrome 的代理。归根到底应该还是FAN墙了。那么一定应该有代理服务器吧,最近比较忙,这个也不去细看了。

天下没有免费的午餐!这个插件可以免费使用一些时间,要想一直使用下去,需要将你 Chrome 启动页设置为 https://2018.hao245.com ,再打开 Chrome 时会从 https://2018.hao245.com 跳转到 https://www.2345.com/?39291 ,我想这就是插件开发者的盈利方式吧。下面是2345网址导航的推广计费规则页面 http://jifen.2345.com/jifen/navigate,非2345浏览器访问是每天每用户是5分钱。

其实我对 2345 的东西没有任何信任。因为之前有朋友使用2345好压这个工具,压缩了重要的业务资料并且设置了密码。可悲的是经过几次2345好压软件升级后,压缩包打开不开了。联系客服根本没有人回复。所以,2345旗下软件慎用!!!

时间:2019年2月24日 星期日

天气:温度 -9°,微风

这是2019高校跑的第一站哈尔滨工业大学。

下午3点来到工大体育场,门还是锁着。我一直不知道这地方平时开不开放,反正我来过几次都是锁着的。我一直想在这中大场地里面跑跑,到现在还没找到能进去的高校场地。

足球场也有跑道,之前也在这跑过,就在这跑跑吧。

小遗憾

  • 下午4点时天就有些冷了
  • 还起了风,我穿的太薄,感觉有些失温
  • 就在体育场里面跑,没有把学校整个浏览一遍

背景

最近在个项目里使用了人人开源项目 renren-fast | Java 快速开发平台。

为了让项目的包结构清晰整洁、代码维护容易,就创建了 top.lichenliang.santa 这个包,项目中的所有模块的实现都放到这个包中。

熟悉 Spring Boot 的都知道,Spring Boot 项目启动时默认只扫描有注释 @SpringBootApplication 这个类所在包下的类和子包。

配置方式

除了需要为 Spring Boot 配置要扫描的包外,还需要为 MyBatis 手动配置 Mapper 的路径。

下面就是配置就是如何才能增加另外的项目包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class} , scanBasePackages={"io.renren","top.lichenliang.santa"})
@MapperScan({"io.renren.**.dao" , "top.lichenliang.santa.**.dao"})
@Import({DynamicDataSourceConfig.class})
public class RenrenApplication extends SpringBootServletInitializer {

public static void main(String[] args) {
SpringApplication.run(RenrenApplication.class, args);
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(RenrenApplication.class);
}

}

此代码由阿伟提供 :)