澳门太阳娱乐集团官网-太阳集团太阳娱乐登录

ES6 Promise的resolved深远通晓
分类:脚本专栏

最近在看方方的《造轮子》,我是直接上 vue-cli 3 的,所以在配置上真的下了很大功夫。今天就出现了在循环里调用 resolve 函数的问题。

Promise的概念在ES6标准推出来之前已经深入人心,很多框架和第三方库都有类似的实现。但在深入理解ES6的Promise对象的时候,受之前经验的影响,很多概念给人似是而非的感觉,其中有一个特别明显的地方就是ES6中对Promise对象状态的定义以及resolved概念。

Promise如何标记resolve或者reject

先说说我的配置吧,我是使用 vue-cli 3 + vue-cli-plugin-unit-karma 插件来配置的 Karma 的。

Promise的状态

Promise对象有三个状态:pending,fulfilled,rejected,MDN文档上对Promise对象状态变化过程用一幅图描述:

图片 1

图中有几个小细节:

  1. 图中的fulfill和reject只是表示指向不同结果,而不是指一个过程,在到达fulfilledrejected状态前promise都处在pending状态。
  2. settled包括了fulfilledrejected
  3. promise只会在pendingfulfilledrejected三种状态下切换。

在根Promise对象里面

根对象的创建一般是通过显式的new一个Promise对象而创建

new Promise( (resolve, reject) => {
  if (...) {
     resolve(value1)
  }
  else {
     reject(value2)
  }
})

方法1: 调用resolve或者reject来标记状态变迁
例如

new Promise((resolve, reject) => { resolve("SUCC") })
.then((value)   => { console.log("THEN : " + value) })
.catch((reason) => { console.log("THROW: " + reason) })


new Promise((resolve, reject) => { reject("FAIL") })
.then((value)   => { console.log("THEN : " + value) })
.catch((reason) => { console.log("THROW: " + reason) })

运行结果

$ node test.js
THEN : SUCC
THROW: FAIL

方法2:通过抛出异常(throw)指令来标记rejected状态变迁
注意不能用return语句来标记resolved状态变迁,这个return值不知道会被返回到哪里去。

例子

new Promise((resolve, reject) => { return "SUCC" })
.then((value)   => { console.log("THEN : " + value) })
.catch((reason) => { console.log("THROW: " + reason) })


new Promise((resolve, reject) => { throw "FAIL" })
.then((value)   => { console.log("THEN : " + value) })
.catch((reason) => { console.log("THROW: " + reason) })

运行结果

$ node p.js
THROW: FAIL

可以看到return语句标记的Promise既不是resolved状态也不是rejected状态,也就是他没有发生状态变迁。

vue.config.js

vue-cli 3 是不需要 webpack.config.js 的,所以要将配置 Karam 要使用 vue-cli-plugin-unit-karma 插件,这也是官方推荐的方法,而且这个插件是和 vue-cli-service 配合的连 package.json 里的 test 都不用改,直接 vue-cli-service test:unit 一波带走。这里面我还使用了全局 Sass 文件。

const karmaConfig = require('./karma.config')module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/overwatch-ui/' : '/', css: { loaderOptions: { sass: { // @ -> /src data: ` @import "@/assets/styles/global.scss"; @import "@/assets/styles/reset.scss"; ` } } }, pluginOptions: { karma: { expressServer: undefined, karmaConfig: karmaConfig } }}

resolved 和 unresolved

事情在现在看来都很美好,状态清晰,一目了然。然而,在各种教程中我们会看到这样一个状态:resolved,阮老师的ECMAScript 6 入门中直接把它当作了fulfilled状态:

Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。

但是MDN中的备注是这样写的:

你可能也会听到一个术语resolved,它表示Promise对象处于settled状态,或者Promise对象被锁定在了调用链中。

嗯?记不记得上面提到settled状态包括fulfilledrejected状态,那么他至少不能和fulfilled状态划等号吧?于是我又搜了搜,发现网上很多文章提到resolved这个词时,有人直接当作fulfilled状态,有人把它理解为complete(类似地,fulfilled是success,rejected自然就是error或者failure),不过更多人都是很含糊地引用了一下文档或者阮老师的文章。

不过确实有人直接提出了这个问题:Promise的fulfill和resolve为啥要用两个词?,我着重看了贺师俊老师的回答:

因为 fulfill 和 resolve 是不同的。
resolve 只是表示这个 promise 已经确定了,比如 promise A 被 resolve 为 promise B,则 A 已经 resolved 但是并未 fulfilled 。

好吧,我承认我一开始看没看懂...
不过还好在备注那里MDN放出了一篇帮助理解的文章,Domenic Denicola 的 States and Fates,基本把这两个概念说清楚了,不过文中只有简单的文字描述,下面说一下我自己的理解。

在非根的Promise链里面

方法1:通过返回(return)来标记resolved状态和抛出异常(throw)来标记rejected状态

new Promise((resolve, reject) => { resolve("SUCC") })
.then((value)   => { console.log("THEN-1 : " + value); return "SUCC1" })
.then((value)   => { console.log("THEN-2 : " + value); })
.then((value)   => { console.log("THEN-3 : " + value); })
.catch((reason) => { console.log("THROW: " + reason) })

运行

$ node p.js
THEN-1 : SUCC
THEN-2 : SUCC1
THEN-3 : undefined

return语句标记状态为resolved;如果没有返回(return)语句,那么相当于直接返回,没有返回值,对后一个Promise将收到undefined值作为参数。

rejected的例子

new Promise((resolve, reject) => { resolve("SUCC") })
.then((value)   => { console.log("THEN-1 : " + value); throw "FAIL" })
.then((value)   => { console.log("THEN-2 : " + value); return })
.then((value)   => { console.log("THEN-3 : " + value); })
.catch((reason) => { console.log("THROW: " + reason) })

运行结果

$ node p.js
THEN-1 : SUCC
THROW: FAIL

throw语句导致Promise状态变迁为rejected。

方法2:创建一个新的Promise对象,通过新Promise里面显式的调用resolve或者reject来标记状态

new Promise((resolve, reject) => { resolve("SUCC") })
.then((value)   => { console.log("THEN-1 : " + value); return new Promise((resolve, reject) => { resolve("SUCC1") }) })
.then((value)   => { console.log("THEN-2 : " + value); })
.catch((reason) => { console.log("THROW: " + reason) })

运行结果

$ node p.js
THEN-1 : SUCC
THEN-2 : SUCC1

karam.config.js

注意,这不是官方所用的那种 karam.config.js 文件,我的文件只是返回一个对象,不想在 vue.config.js 里写太长了而已。

module.exports = { basePath: '', frameworks: ['mocha', 'chai', 'sinon-chai'], client: { chai: { includeStack: true }, }, files: [ 'tests/**/*.spec.js', 'tests/**/*.spec.ts', ], reporters: ['progress'], port: 9876, colors: true, autoWatch: true, browsers: ['ChromeHeadless'], concurrency: Infinity,}

state 和 fates

文章将Promise分得更细,分为了状态(state)和结果(fates,命运,原谅我粗浅的翻译)。

状态(state)就是上面提到的三种状态,需要稍微留意一下的是,文中提到settled不是一种Promise状态,只是一种语义上的便利,说白了就是描述Promise确定了而已。

结果(fates)分为resolvedunresolved,两种结果互斥。

  • resolved,原文解释有点绕:

    A promise is resolved if trying to resolve or reject it has no effect,当没办法去resolve或reject一个promise的时候,这个promise就是resolved的。

  • unresolved,原文解释倒是很简单:非resolved的promise...... (:з」∠)

显而易见,只要理解了resolved的描述,自然就理解unresolved。首先先提一个Promise的概念,即当Promise的状态一旦改变,就永久保持该状态。所以什么时候没办法去resolve或reject一个promise对象呢?

  1. 处于fulfilledrejected状态的promise对象,因为他们已经无法改变状态,而resolve和reject只能对pending状态的promise有效。
  2. 处于pending状态的promise对象也有可能处于resolved,当promise对象被'锁定'在另外一个promise对象或者一个非即时返回的thenable函数中时,也就是上面贺老师提到的promise A 被 resolve 为 promise B这种情况,为了帮助理解,下面用代码举个例子。

说明:Chrome(57.0.2987.133)打印promise对象的时候,[[PromiseStatus]]有三个值:pending,resolved,rejected,这里的resolved状态就是指fulfilled状态,和我们要说明的resolved没有关系,在firefox(49.0.1)中,[[PromiseStatus]]三个值和标准相同:pending,fulfilled,rejected。为了避免混淆,用firefox做测试

先上一个简单的代码:

var p1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 'p1 resolve');
});
p1.then(value => {
    console.log(value);   // p1 resolve
    console.log(p1);   //  Promise { <state>: "fulfilled", <value>: "p1 resolve" }
})

很简单,一秒后打印p1 resolve,p1对象状态为fulfilled。再来看第二例子,在resolve方法中新建了一个Promise对象:

var p1 = new Promise((resolve, reject) => {
    function F() {
        resolve(new Promise((resolve, reject) => {
            console.log(p1);  //  Promise { <state>: "pending" }
            setTimeout(resolve, 2000, 'new Promise resolve');
        }))
    }
    setTimeout(F, 1000, 'p1 resolve');
});
p1.then(function(value) {
    console.log(value);  //  new Promise resolve
    console.log(p1);  //  Promise { <state>: "fulfilled", <value>: "new Promise resolve" }
})

运行一下发现,一秒后打印

Promise { <state>: "pending" }

三秒后打印

new Promise resolve
Promise { <state>: "fulfilled", <value>: "new Promise resolve" }

可以看到,一秒后p1调用了resolve方法,和上一个例子一样被resolve了,理应变为fulfilled状态,但是他被'锁定'在了一个新的Promise对象中,所以状态没有立刻改变并执行onfulfilled方法,而是依然处于pending状态,但是很明显的是,他的最终结果(这里可以体会到称为fate的原因,有种宿命的味道)就是fulfilled状态,此时的p1不能再resolve或reject。再过了两秒后新Promise对象resolve,p1执行then(),状态变为fulfilled,打印的值也变成了new Promise resolve,而不是p1 resolve。此时打印出的p1的状态就是fulfilled。

到此,处于pending状态的promise对象也有可能处于resolved情况就很清楚了,至于unresolved,就是promise可以被resolve或reject的时候,此时promise对象一定处于pending状态且没有被resolve或reject为其他Promise对象 / 非即时返回的thenable函数。而反过来,处于pending状态的promise对象不一定是unresolved。

Input.spec.js

先定义一下 testProperty 函数,因为我理想的情况是多种值都要测,所以应该循环一个数组,对数组里每个值都做一次测试(就是这个代码坑了,用回调好好的,自己非得装B用 Promise 来玩)。

function testProperty { return new Promise((resolve, reject) => { values.forEach => resolve })}

这里我就不写那么多代码啦,大家看到这就意会一下就行了。

describe('events', ()=> { it('can be handled', () => { const events = ['click', 'change', 'input'] const eventHandler = sinon.stub() testProperty .then((eventName) => { console.log(eventName) const InputVue = shallowMount(Input, { listeners: { [`${eventName}`]: eventHandler } }) InputVue.find.trigger(eventName) expect(eventHandler.called).to.equal })})

说完上面的基本配置,就说说今天的采坑记吧。

当我运行 yarn run test 的时候,发现 Karma 跑通了,Yes,第一波 Vue + Karam 配置成功!

图片 2

哎不对,我的 console.log(eventName) 怎么没了?第一反应是 Karam 没有配置好,因为测试是归 Karma 管的。然后 Google 了一下,找到了 Karam runner 里 Karam-Mocha 里的 Issue

图片 3

然后马上去将 browserConsoleLogOptionscaptureConsole 里的东西加在 karam.config.js 里。再次运行,Boom,还是不行,什么都没有打印。

虽然这个人很多赞,但是毕竟是个人说辞,不太可靠,他既然说到了 Karma 的配置,那么官方文档肯定会有说呀。Go,下一步 Karam 官方文档。果然我找到一个配置:

图片 4

说是在 karam.config.js 里设置 config.LOG_INFO 的,像这样:

module.exports = function { config.set({ logLevel: config.LOG_INFO, });};

但是,这种是不是 vue cli 3 的写法,这是 vue cli + webpack.config.js 的写法呀。我了个去,这么写 config 都不知道从哪来,常量 LOG_INFO 肯定是 undefined

哎,虽然我不能配置 logLevel: config.LOG_INFO,但是我可以用命令行呀,所以马上去 package.json 里改脚本命令:

"test": "vue-cli-service test:unit --log-level debug"

开 iTerm2,秒写命令:

yarn run test

但是还是没 log,当时心态已经到崩溃的边缘了。

现在头号嫌疑犯就是这个插件了,因为 --log-level debug 写在 vue-cli-service 后面,应该是要 vue-cli-service 结合使用的,而这个插件号称自己和 vue-cli-service 完美结合,一看版本才 0.5 想:是不是你这个弱智没写好插件就到处装B了。又去看了这个插件的 Issue。

图片 5

嗯。。。还很干净。没法了,现在只有两条路

  1. 写 Issue 给这个插件作者,可能要等一会才能解决,而且不一定能解决。
  2. 使用降级 vue-cli,使用 vue-cli 和 webpack.config.js 结合的老方法,一定能行,但是麻烦。

两个方法都很难受,所以我选择了第三种方法:在别的地方打 log,是不是真的别的地方都不能打 log 了。所以我在 testProperty 里加了句:

function testProperty { return new Promise((resolve, reject) => { console.log values.forEach => resolve })}

结果竟然有 log 了!看来上面的方法全部作废了,别的地方能 log 说明配置完全正确。

其实我一开始写的 testProperty 是这样的:

function testProperty(values, callback) { values.forEach}

我想了想,是不是有点 low 啊,这样封装好像都没什么意义,所以使用了 Promise 写法。而这种写法只能在循环里调用 resolve 才能将数组元素传入回调里。后面自己再试了试,then() 完了之后只会调用一次 resolve,而不会在循环里多次调用,我的这种写法纯粹属性猜测去写,所以导致看了半天的 Issue 和 StackOverflow 才慢慢解决了这个问题。

其实我的循环调用 resolve 是可以打出一个 log 的,因为会调用一次,但是前期确实没有将 karam.config.js 里的 clientbrowserConsoleLogOptions 配置好,所以没有 log。

即使加了这个配置选项,可能当时代码本来也点问题,所以一直没发现这种”猜测写法“带来的问题。最后解决的时候确实能回想当初写这样代码的想法:“能不能循环 resolve 呢?嗯试试吧”。

总之,这是一次不错的调错经历,调完真的是身心俱疲。

小结

到这里Promise对象的状态和resolved的概念差不多都清楚了,这篇文章也是我个人理解过程的一个记录。不过参考到的文章不多,文中的解释可能并不是很准确,所以在这里贴出来,希望能看看其他人的理解。

一些闲话

说起来,这篇文章离我上一篇文章已经很久了,真是光阴似箭,岁月......算了,还是不感慨了。现在工作确实没有在学校那会儿充足的时间和精力,总结还是有不少,大部分都在个人的笔记里,没有贴到博客。有些觉得总结份量不够,有些觉得个人的理解不深,不敢随意贴出来。不过在学习一些稍微深入一点的概念的时候,总觉得网上的一些资料并不是很全面和客观,人云亦云的东西太多,自己的理解也是七七八八。所以希望能够贴出来和大家交流,防止自己闭门造车,后续如果工作时间允许,会发一些这样的文章,对前端一些比较重要的知识点深入理解。

本文来源:JuFoFu

本文地址:

参考文档:

Domenic Denicola . States and Fates

阮一峰 . Promise 对象

MDN . Promise

水平有限,错误欢迎指正。原创博文,转载请注明出处。

本文由澳门太阳娱乐集团官网发布于脚本专栏,转载请注明出处:ES6 Promise的resolved深远通晓

上一篇:没有了 下一篇:没有了
猜你喜欢
热门排行
精彩图文