diff --git a/docs/async.md b/docs/async.md index 1c6ab57..d922b41 100644 --- a/docs/async.md +++ b/docs/async.md @@ -661,7 +661,7 @@ async function logInOrder(urls) { 这里隐含着一个规定,`next`方法必须是同步的,只要调用就必须立刻返回值。也就是说,一旦执行`next`方法,就必须同步地得到`value`和`done`这两个属性。如果遍历指针正好指向同步操作,当然没有问题,但对于异步操作,就不太合适了。目前的解决方法是,Generator 函数里面的异步操作,返回一个 Thunk 函数或者 Promise 对象,即`value`属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而`done`属性则还是同步产生的。 -目前,有一个[提案](https://github.com/tc39/proposal-async-iteration),为异步操作提供原生的遍历器接口,即`value`和`done`这两个属性都是异步产生,这称为”异步遍历器“(Async Iterator)。 +ES2018 [引入](https://github.com/tc39/proposal-async-iteration)了”异步遍历器“(Async Iterator),为异步操作提供原生的遍历器接口,即`value`和`done`这两个属性都是异步产生。 ### 异步遍历的接口 diff --git a/docs/object.md b/docs/object.md index baa1d44..c524972 100644 --- a/docs/object.md +++ b/docs/object.md @@ -1255,11 +1255,11 @@ a // 1 b // [2, 3] ``` -ES2017 将这个运算符[引入](https://github.com/sebmarkbage/ecmascript-rest-spread)了对象。 +ES2018 将这个运算符[引入](https://github.com/sebmarkbage/ecmascript-rest-spread)了对象。 -**(1)解构赋值** +### 解构赋值 -对象的解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。 +对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。 ```javascript let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; @@ -1331,7 +1331,7 @@ function baseFunction({ a, b }) { // ... } function wrapperFunction({ x, y, ...restConfig }) { - // 使用x和y参数进行操作 + // 使用 x 和 y 参数进行操作 // 其余参数传给原始函数 return baseFunction(restConfig); } @@ -1339,9 +1339,9 @@ function wrapperFunction({ x, y, ...restConfig }) { 上面代码中,原始函数`baseFunction`接受`a`和`b`作为参数,函数`wrapperFunction`在`baseFunction`的基础上进行了扩展,能够接受多余的参数,并且保留原始函数的行为。 -**(2)扩展运算符** +### 扩展运算符 -扩展运算符(`...`)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。 +对象的扩展运算符(`...`)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。 ```javascript let z = { a: 3, b: 4 }; @@ -1419,6 +1419,8 @@ let newVersion = { ```javascript let aWithDefaults = { x: 1, y: 2, ...a }; // 等同于 + even if property keys don’t clash, because objects record insertion order: + let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a); // 等同于 let aWithDefaults = Object.assign({ x: 1, y: 2 }, a); @@ -1438,6 +1440,8 @@ const obj = { ```javascript {...{}, a: 1} // { a: 1 } + even if property keys don’t clash, because objects record insertion order: + ``` 如果扩展运算符的参数是`null`或`undefined`,这两个值会被忽略,不会报错。 @@ -1468,45 +1472,3 @@ let runtimeError = { }; ``` -## Null 传导运算符 - -编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取`message.body.user.firstName`,安全的写法是写成下面这样。 - -```javascript -const firstName = (message - && message.body - && message.body.user - && message.body.user.firstName) || 'default'; -``` - -这样的层层判断非常麻烦,因此现在有一个[提案](https://github.com/claudepache/es-optional-chaining),引入了“Null 传导运算符”(null propagation operator)`?.`,简化上面的写法。 - -```javascript -const firstName = message?.body?.user?.firstName || 'default'; -``` - -上面代码有三个`?.`运算符,只要其中一个返回`null`或`undefined`,就不再往下运算,而是返回`undefined`。 - -“Null 传导运算符”有四种用法。 - -- `obj?.prop` // 读取对象属性 -- `obj?.[expr]` // 同上 -- `func?.(...args)` // 函数或对象方法的调用 -- `new C?.(...args)` // 构造函数的调用 - -传导运算符之所以写成`obj?.prop`,而不是`obj?prop`,是为了方便编译器能够区分三元运算符`?:`(比如`obj?prop:123`)。 - -下面是更多的例子。 - -```javascript -// 如果 a 是 null 或 undefined, 返回 undefined -// 否则返回 a.b.c().d -a?.b.c().d - -// 如果 a 是 null 或 undefined,下面的语句不产生任何效果 -// 否则执行 a.b = 42 -a?.b = 42 - -// 如果 a 是 null 或 undefined,下面的语句不产生任何效果 -delete a?.b -``` diff --git a/docs/promise.md b/docs/promise.md index c87f055..4793c58 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -486,6 +486,85 @@ someAsyncThing().then(function() { 上面代码中,第二个`catch`方法用来捕获,前一个`catch`方法抛出的错误。 +## Promise.prototype.finally() + +`finally`方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。 + +```javascript +promise +.then(result => {···}) +.catch(error => {···}) +.finally(() => {···}); +``` + +上面代码中,不管`promise`最后的状态,在执行完`then`或`catch`指定的回调函数以后,都会执行`finally`方法指定的回调函数。 + +下面是一个例子,服务器使用 Promise 处理请求,然后使用`finally`方法关掉服务器。 + +```javascript +server.listen(port) + .then(function () { + // ... + }) + .finally(server.stop); +``` + +`finally`方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是`fulfilled`还是`rejected`。这表明,`finally`方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。 + +`finally`本质上是`then`方法的特例。 + +```javascript +promise +.finally(() => { + // 语句 +}); + +// 等同于 +promise +.then( + result => { + // 语句 + return result; + }, + error => { + // 语句 + throw error; + } +); +``` + +上面代码中,如果不使用`finally`方法,同样的语句需要为成功和失败两种情况各写一次。有了`finally`方法,则只需要写一次。 + +它的实现也很简单。 + +```javascript +Promise.prototype.finally = function (callback) { + let P = this.constructor; + return this.then( + value => P.resolve(callback()).then(() => value), + reason => P.resolve(callback()).then(() => { throw reason }) + ); +}; +``` + +上面代码中,不管前面的 Promise 是`fulfilled`还是`rejected`,都会执行回调函数`callback`。 + +从上面的实现还可以看到,`finally`方法总是会返回原来的值。 + +```javascript +// resolve 的值是 undefined +Promise.resolve(2).then(() => {}, () => {}) + +// resolve 的值是 2 +Promise.resolve(2).finally(() => {}) + +// reject 的值是 undefined +Promise.reject(3).then(() => {}, () => {}) + +// reject 的值是 3 +Promise.reject(3).finally(() => {}) +``` + ## Promise.all() `Promise.all`方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 @@ -747,64 +826,6 @@ Promise.reject(thenable) 上面代码中,`Promise.reject`方法的参数是一个`thenable`对象,执行以后,后面`catch`方法的参数不是`reject`抛出的“出错了”这个字符串,而是`thenable`对象。 -## 两个有用的附加方法 - -ES6 的 Promise API 提供的方法不是很多,有些有用的方法可以自己部署。下面介绍如何部署两个不在 ES6 之中、但很有用的方法。 - -### done() - -Promise 对象的回调链,不管以`then`方法或`catch`方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise 内部的错误不会冒泡到全局)。因此,我们可以提供一个`done`方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。 - -```javascript -asyncFunc() - .then(f1) - .catch(r1) - .then(f2) - .done(); -``` - -它的实现代码相当简单。 - -```javascript -Promise.prototype.done = function (onFulfilled, onRejected) { - this.then(onFulfilled, onRejected) - .catch(function (reason) { - // 抛出一个全局错误 - setTimeout(() => { throw reason }, 0); - }); -}; -``` - -从上面代码可见,`done`方法的使用,可以像`then`方法那样用,提供`fulfilled`和`rejected`状态的回调函数,也可以不提供任何参数。但不管怎样,`done`都会捕捉到任何可能出现的错误,并向全局抛出。 - -### finally() - -`finally`方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。它与`done`方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。 - -下面是一个例子,服务器使用 Promise 处理请求,然后使用`finally`方法关掉服务器。 - -```javascript -server.listen(0) - .then(function () { - // run test - }) - .finally(server.stop); -``` - -它的实现也很简单。 - -```javascript -Promise.prototype.finally = function (callback) { - let P = this.constructor; - return this.then( - value => P.resolve(callback()).then(() => value), - reason => P.resolve(callback()).then(() => { throw reason }) - ); -}; -``` - -上面代码中,不管前面的 Promise 是`fulfilled`还是`rejected`,都会执行回调函数`callback`。 - ## 应用 ### 加载图片 diff --git a/docs/proposals.md b/docs/proposals.md index e4e479f..bc68caa 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -117,3 +117,46 @@ class Product { 上面代码中,`throw`都出现在表达式里面。 语法上,`throw`表达式里面的`throw`不再是一个命令,而是一个运算符。为了避免与`throw`命令混淆,规定`throw`出现在行首,一律解释为`throw`语句,而不是`throw`表达式。 + +## Null 传导运算符 + +编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取`message.body.user.firstName`,安全的写法是写成下面这样。 + +```javascript +const firstName = (message + && message.body + && message.body.user + && message.body.user.firstName) || 'default'; +``` + +这样的层层判断非常麻烦,因此现在有一个[提案](https://github.com/claudepache/es-optional-chaining),引入了“Null 传导运算符”(null propagation operator)`?.`,简化上面的写法。 + +```javascript +const firstName = message?.body?.user?.firstName || 'default'; +``` + +上面代码有三个`?.`运算符,只要其中一个返回`null`或`undefined`,就不再往下运算,而是返回`undefined`。 + +“Null 传导运算符”有四种用法。 + +- `obj?.prop` // 读取对象属性 +- `obj?.[expr]` // 同上 +- `func?.(...args)` // 函数或对象方法的调用 +- `new C?.(...args)` // 构造函数的调用 + +传导运算符之所以写成`obj?.prop`,而不是`obj?prop`,是为了方便编译器能够区分三元运算符`?:`(比如`obj?prop:123`)。 + +下面是更多的例子。 + +```javascript +// 如果 a 是 null 或 undefined, 返回 undefined +// 否则返回 a.b.c().d +a?.b.c().d + +// 如果 a 是 null 或 undefined,下面的语句不产生任何效果 +// 否则执行 a.b = 42 +a?.b = 42 + +// 如果 a 是 null 或 undefined,下面的语句不产生任何效果 +delete a?.b +``` diff --git a/docs/string.md b/docs/string.md index 1e6528d..770ed08 100644 --- a/docs/string.md +++ b/docs/string.md @@ -933,7 +933,7 @@ Breve over the h goes \u{h}ere // 报错 模板字符串会将`\u00FF`和`\u{42}`当作 Unicode 字符进行转义,所以`\unicode`解析时报错;而`\x56`会被当作十六进制字符串转义,所以`\xerxes`会报错。也就是说,`\u`和`\x`在 LaTEX 里面有特殊含义,但是 JavaScript 将它们转义了。 -为了解决这个问题,现在有一个[提案](https://tc39.github.io/proposal-template-literal-revision/),放松对标签模板里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回`undefined`,而不是报错,并且从`raw`属性上面可以得到原始字符串。 +为了解决这个问题,ES2018 [放松](https://tc39.github.io/proposal-template-literal-revision/)了对标签模板里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回`undefined`,而不是报错,并且从`raw`属性上面可以得到原始字符串。 ```javascript function tag(strs) {