目录
前言ES7概述1.对象条目2.对象值3. String.prototype.padEnd4. String.prototype.padStart5. Object.getOwnPropertyDescriptor6.尾随逗号7. SharedArrayBuffer8.原子学9.异步功能前言
您好,过去我已经考虑过ES6的创新,现在是时候拆分ES8了,因为它带来了许多新功能。 我没有单独考虑ES7(2016),因为此版本仅带来了2项创新。 这是Array.prototype.includes()和幂运算符。 但是,在启动ES8之前,让我们先看看ES7的创新。
ES7概述
includes()方法确定数组是否包含特定元素,并根据此结果返回true或false。
Array.prototype.includes(searchElement[, fromIndex = 0]) : Boolean
searchElement-要搜索的项目。
fromIndex-数组中开始搜索searchElement元素的位置。 对于负值,将从索引array.length + fromIndex升序开始执行搜索。 默认值为0。
例子 [1, 2, 3].includes(2);
include()可以应用于其他类型的对象(例如,类似数组的对象)。 示例:在arguments对象上使用include()方法。
(function() { console.log([].includes.call(arguments, 'a'));
幂运算符 (**)返回以a为底且自然指数为b的幂。 提高a到b的力量。
a ** b
例子 2 ** 3
1.对象条目
Object.entries()返回一个数组,其元素是与直接在对象中发现的[key,value]对的枚举属性相对应的数组。 属性的顺序与您手动循环浏览对象的属性时的顺序相同。
Object.entries(obj) : Array
obj-一个对象,其枚举属性将作为数组[key,value]返回。
Object.entries()返回属性的顺序与for ... in循环中的顺序相同(不同之处是for-in还列出了原型链中的属性)。 Object.entries()返回的数组中元素的顺序与声明对象的方式无关。 如果需要特定顺序,则必须在调用方法之前对数组进行排序。
例子 var obj = { foo: "bar", baz: 42 }; console.log(Object.entries(obj));
将对象转换为地图新的Map()构造函数接受值的重复。 使用Object.entries,您可以轻松地将Object转换为Map。 这比使用2个元素数组组成的数组更为简洁,但是键只能是字符串。
var obj = { foo: "bar", baz: 42 }; var map = new Map(Object.entries(obj)); console.log(map);
为什么Object.entries()的返回值是数组而不是迭代器?
在这种情况下,对应的用例是Object.keys(),而不是Map.prototype.entries()。
为什么Object.entries()仅返回带有字符串键的枚举本机属性?
再次,这样做是为了匹配Object.keys()。 此方法还忽略键为字符的属性。 最后,可能会有一个Reflect.ownEntries()方法返回其自身的所有属性。
请参阅官方
规范以及
MDN Web文档中的object.entries。
2.对象值
Object.values()返回一个数组,其元素是在对象中找到的枚举属性的值。 该顺序与您手动循环浏览对象的顺序相同。
Object.values(obj) : Array
obj-将返回其枚举属性值的对象。
Object.values()方法以与for ... in循环相同的顺序返回对象的枚举属性的值的数组。 循环和方法之间的区别在于,循环列出了原型链中的属性。
例子 var obj = { foo: "bar", baz: 42 }; console.log(Object.values(obj));
Object.entries和Object.values()之间的区别在于,第一个返回包含属性名称和值的数组的数组,而第二个仅返回具有属性值的数组。
Object.values()和Object.entries()之间的示例差异 const object = { a: 'somestring', b: 42, c: false }; console.log(Object.values(object));
请参阅官方
规范中的Object.values()以及
MDN Web文档 。
3. String.prototype.padEnd
padEnd()方法使用给定的字符串(最终重复)来完成当前行,以便结果字符串达到指定的长度。 加法应用于当前行的末尾(右侧)。
String.prototype.padEnd(maxLength [ , fillString ]) : String
maxLength-填充当前行后结果行的长度。 如果此参数小于当前行的长度,则将按原样返回当前行。
fillString-用于补充当前行的字符串。 如果此行太长,它将被截断并且将应用最左边的那一行。 “”(0x0020 SPACE)是此参数的默认值。
例子 'abc'.padEnd(10);
填充字符串的用例包括:
- 在文件名或URL中添加计数器或标识符:'file 001.txt'
- 控制台输出对齐方式:“测试001:✓”
- 打印带有固定位数的十六进制或二进制数字:'0x00FF'
请参阅官方
规范中的String.prototype.padEnd以及
MDN Web文档 。
4. String.prototype.padStart
padStart()方法用另一行填充当前行(如果需要,可以多次输入),以使结果行达到指定的长度。 填充在当前行的开头(左侧)执行。
String.prototype.padStart(maxLength [, fillString]) : String
maxLength-当前行完成后摘要行的长度。 如果该值小于当前行的长度,则当前行将保持不变。
fillString-填充当前行的字符串。 如果此字符串对于给定的长度而言太长,它将被截断。 默认值为“”(0x0020空格)。
例子 'abc'.padStart(10);
为什么未将填充方法称为padLeft和padRight?
对于双向或从右到左语言,术语“左”和“右”不起作用。 因此,padStart和padEnd的命名遵循现有的名称,以startsWith和endsWith开头。
请参阅官方
规范以及
MDN Web文档中的String.prototype.padStart。
5. Object.getOwnPropertyDescriptor
Object.getOwnPropertyDescriptor()方法为传递的对象返回自己的属性(即直接位于对象中,而不是通过原型链接收到的属性)的属性描述符。 如果该属性不存在,则返回undefined。
Object.getOwnPropertyDescriptor(obj, prop) : Object
obj-在其中搜索属性的对象。
prop-将返回其描述的属性名称。
此方法使您可以查看属性的确切描述。 JavaScript中的属性由字符串名称和属性描述符组成。
属性描述符是具有以下某些属性的记录:
- value-与属性关联的值(仅在数据描述符中)。
- 可写-如果可以更改与属性关联的值,则为true;否则为false(仅在数据描述符中)。
- get-返回属性值的函数,如果没有这样的函数,则为undefined(仅在访问描述符中)。
- set-更改属性值的函数,如果没有这样的函数,则为undefined(仅在访问描述符中)。
- 可配置-如果可以更改此属性的句柄类型,并且可以从包含它的对象中删除该属性,则为true,否则为false。
- 枚举-如果列出包含它的对象的属性时此属性可用,则为true,否则为false。
例子 obj = { get foo() { return 10; } }; console.log(Object.getOwnPropertyDescriptor(obj, 'foo'));
Object.getOwnPropertyDescriptor()的用例
第一个用例:将属性复制到对象
从ES6开始,JavaScript已经具有用于复制属性的工具方法:Object.assign()。 但是,此方法使用简单的get和set操作复制键为键的属性:
const value = source[key];
这意味着它无法正确复制具有默认指定属性以外的属性的属性(获取,设置,写入等方法)。 以下示例说明了此限制。 该对象的源有一个安装程序,其密钥为foo:
const source = { set foo(value) { console.log(value); } }; console.log(Object.getOwnPropertyDescriptor(source, 'foo'));
使用Object.assign()将foo属性复制到目标对象失败:
const target1 = {}; Object.assign(target1, source); console.log(Object.getOwnPropertyDescriptor(target1, 'foo'));
幸运的是,结合使用Object.getOwnPropertyDescriptors()和Object.defineProperties()可以:
const target2 = {}; Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source)); console.log(Object.getOwnPropertyDescriptor(target2, 'foo'));
第二个用例:克隆对象
浅克隆类似于复制属性,因此Object.getOwnPropertyDescriptors()在这里也是一个不错的选择。
这次我们使用Object.create(),它具有两个参数:
第一个参数指定返回对象的原型。
第二个可选参数是属性描述符的集合,类似于Object.getOwnPropertyDescriptors()返回的那些描述符。
const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
第三种用例:具有任意原型的跨平台对象文字。
使用对象文字来创建具有任意原型的对象的最佳语法方法是使用特殊的__proto__属性:
const obj = { __proto__: prot, foo: 123, };
las,保证仅在浏览器中提供此功能。 常规解决方法是Object.create()和赋值:
const obj = Object.create(prot); obj.foo = 123;
但是您也可以使用Object.getOwnPropertyDescriptors():
const obj = Object.create( prot, Object.getOwnPropertyDescriptors({ foo: 123, }) );
另一种选择是Object.assign():
const obj = Object.assign( Object.create(prot), { foo: 123, } );
陷阱:使用超级复制方法。
超级使用的方法紧密地绑定到其原始对象(存储该对象的对象)。 当前无法将这种方法复制或移动到另一个对象。
请参阅官方
规范以及
MDN Web文档中的Object.getOwnPropertyDescriptor。
6.尾随逗号
挂逗号(尾随逗号)-在向JavaScript代码添加新元素,参数或属性时很有用。 如果要添加新属性,则只需添加新行而不更改前一行(如果已在其中使用了逗号)。 这使得版本控制更简洁,代码更改的麻烦也更少。
文字中的逗号
数组JavaScript会忽略数组中的逗号分隔:
var arr = [ 0, 1, 2, ]; console.log(arr);
如果使用多个悬挂点,则会创建孔。 具有“孔”的阵列称为稀疏(密集阵列不具有“孔”)。 例如,使用Array.prototype.forEach()或Array.prototype.map()迭代数组时,将跳过孔。
对象 var object = { foo: "bar", baz: "qwerty", age: 42, }; console.log(object);
在功能中使用逗号
参数定义功能参数的以下定义是有效的,并且彼此等效。 悬挂的逗号不会影响函数或其参数对象的length属性。
function f(p) {} function f(p,) {} (p) => {}; (p,) => {};
方法定义挂逗号也可用于定义类或对象的方法。
class C { one(a,) {}, two(a, b,) {}, } var obj = { one(a,) {}, two(a, b,) {}, };
函数调用以下函数调用是有效的,并且彼此等效。
f(p); f(p,); Math.max(10, 20); Math.max(10, 20,);
无效的挂逗号定义函数参数或调用仅包含逗号的函数将引发SyntaxError。 另外,使用其余参数时,不允许使用逗号分隔。
function f(,) {}
吊销中的逗号
使用破坏性分配时,也可以在左侧使用逗号分隔。
再次使用其余参数,将引发SyntaxError。
var [a, ...b,] = [1, 2, 3];
JSON悬挂逗号
仅在ECMAScript 5中允许在对象中挂逗号。由于JSON基于早于ES5的JavaScript语法
,因此JSON中不允许
挂逗号。这两行都抛出SyntaxError
JSON.parse('[1, 2, 3, 4, ]'); JSON.parse('{"foo" : 1, }');
为什么挂逗号有用?
有两个好处。
首先,重新排列元素更容易,因为如果最后一个元素改变了位置,则无需添加或删除逗号。
其次,它可以帮助版本控制系统跟踪真正发生的变化。 例如,来自:
[ 'Foo' ] : [ 'Foo', '' ]
导致带有“ foo”的行和带有“ bar”的行都被标记为已更改,尽管唯一的实际更改是添加最后一行。
请参见
MDN Web文档中的尾随逗号。
7. SharedArrayBuffer
SharedArrayBuffer对象用于创建固定长度的拆分缓冲区,以存储原始二进制数据,类似于ArrayBuffer对象,但是相比之下,SharedArrayBuffer实例可用于在共享内存上创建视图。 SharedArrayBuffer无法断开连接。
new SharedArrayBuffer(length) : Object
length-创建缓冲区数组的大小(以字节为单位)。
返回-指定长度的新SharedArrayBuffer对象。 初始化后其内容为0。
为了在群集中的一个代理与另一个代理(代理可以是网页的主程序或网络工作人员之一)之间使用SharedArrayBuffer对象共享内存,使用了postMessage和结构化克隆。
结构化克隆算法接受映射到SharedArrayBuffers的SharedArrayBuffers和TypedArrays。 在这两种情况下,SharedArrayBuffer对象都传递给接收者,后者在接收代理内部创建一个新的私有SharedArrayBuffer对象(与ArrayBuffer相同)。 但是,两个SharedArrayBuffer对象引用的共享数据块是同一数据块,并且其中一个代理中的该块中的第三方效果最终将在另一个代理中可见。
var sab = new SharedArrayBuffer(1024); worker.postMessage(sab);
共享内存可以在worker或主线程中同时创建和更改。 根据系统(CPU,OS,浏览器)的不同,更改可能需要一段时间才能传播到所有上下文。 为了同步,需要原子操作。
共享数组缓冲区是高级并行抽象的基本构建块。 它们使您可以在多个工作线程和主线程之间共享SharedArrayBuffer对象的字节(共享缓冲区以访问字节,将其包装在Typed Array中)。 这种交换具有两个优点:
您可以更快地在工作人员之间交换数据。
工人之间的协调变得越来越容易和快捷(与postMessage()相比)。
工人的实施如下。
首先,我们提取发送给我们的共享数组的缓冲区,然后将其包装在类型数组中,以便我们可以在本地使用它。
SharedArrayBuffer的属性和方法。SharedArrayBuffer.length-值为1的SharedArrayBuffer构造函数的长度。
SharedArrayBuffer.prototype-允许所有SharedArrayBuffer对象使用其他属性。
SharedArrayBuffer实例属性SharedArrayBuffer.prototype.constructor-定义一个创建对象原型的函数。 初始值为标准的内置SharedArrayBuffer构造函数。
SharedArrayBuffer.prototype.byteLength(只读)-数组的大小,以字节为单位。 在创建数组时设置,无法更改。
方法SharedArrayBuffer.prototype.slice()-返回一个新的SharedArrayBuffer,其内容是此SharedArrayBuffer从独占开始(包括结尾)的字节的副本。 如果开始或结束为负数,则这是指从数组末尾开始的索引,而不是从开头开始。 此方法与Array.prototype.slice()具有相同的算法。
sab.slice([begin, end]) : Object
begin-提取开始的零索引。 您可以使用负索引来指示距序列结尾的偏移量。 slice(-2)提取序列中的最后两个元素。 如果未定义开始,则切片从索引0开始。
结束-应从零开始的索引,应完成提取。
例如,切片(1,4)检索第二个元素到第四个元素(索引为1、2和3的元素)。 您可以使用负索引来指示距序列结尾的偏移量。 slice(2,-1)通过序列中的倒数第二个元素检索第三个元素。 如果省略end,则片将读取序列的末尾(sab.byteLength)。
例子 var sab = new SharedArrayBuffer(1024); sab.slice();
请参阅官方
规范以及
MDN Web文档中的SharedArrayBuffer。
8.原子学
Atomics对象提供原子操作作为静态方法。 与SharedArrayBuffer对象一起使用。
原子操作安装在原子模块中。 与其他全局对象不同,Atomics不是构造函数。 它不能与new运算符一起使用,也不能将Atomics对象作为函数调用。 所有Atomics属性和方法都是静态的(例如,类似于Math对象)。
共享内存时,几个线程可以读取和写入相同的数据到内存。 原子操作保证将读取和读取期望值,并在下一个操作开始工作之前完成操作,并且不会中断这些期望值。
属性
Atomics [Symbol.toStringTag]-此属性的值为Atomics。
方法
原子操作- Atomics.add()-将显示的值添加到数组中指定位置的当前值。 返回此位置的前一个值。
- Atomics.and()-在指定的数组位置计算按位与。 返回此位置的前一个值。
- Atomics.compareExchange()-如果显示的值等于显示的值,则将显示的值保存到数组的指定位置。 返回前一个值。
- Atomics.exchange() — . .
- Atomics.load() — .
- Atomics.or() — OR . .
- Atomics.store() — . .
- Atomics.sub() — . .
- Atomics.xor() — XOR . .
Atomics.add()静态方法将值添加到数组中指定位置的当前值,并在此位置返回前一个值。此原子操作确保在回写修改后的值之前,不会发生其他写操作。 Atomics.add(typedArray, index, value) : mixed
- typedArray-整数的拆分数组。Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array或Uint32Array。
- index-typedArray中要添加值的位置。
- value-要添加的数字。
- return-指定位置的先前值(typedArray [index])。
- 如果typedArray类型不是有效的整数类型之一,则引发TypeError。
- 如果typedArray类型不是泛型类型,则引发TypeError。
- 如果index在typedArray之外,则抛出RangeError。
例子 var sab = new SharedArrayBuffer(1024); var ta = new Uint8Array(sab); Atomics.add(ta, 0, 12);
Atomics.add()中的规范,在反复MDN网络上的文档。等待并通知
wait()和wake()方法是基于futexes(“快速用户空间互斥体”)Linux建模的,并提供了等待直到某个状态变为真的方法,通常用作阻塞构造。Atomics.wait()检查仍表示的值是否包含在数组的指定位置中,并且处于睡眠挂起或超时状态。返回确定,不等于或超时。如果调用代理中不允许等待,则它将引发异常错误(大多数浏览器不允许在浏览器的主流中使用wait())。- Atomics.wait() — , -. «ok», «not-equal» «timed-out». , ( wait() ).
- Atomics.wake() — , . , .
- Atomics.isLockFree(size) — , . true, ( ). .
优化问题
优化使工作人员中的代码不可预测。在单线程中,编译器可以执行破坏多线程代码的优化。以下面的代码为例: while (sharedArray [0] === 123);
在单线程中,sharedArray [0]的值在循环执行期间永不更改(如果sharedArray是数组或未以任何方式固定的类型化数组)。因此,可以对代码进行如下优化: const tmp = sharedArray [0]; while (tmp === 123);
但是,在多线程模式下,此优化不允许我们使用此模板来等待另一个线程中所做的更改。另一个示例是以下代码:
在一个线程中,您可以重新排列这些写操作,因为它们之间什么也没读。当您期望以特定顺序执行记录时,多线程会遇到问题:
这些类型的优化使得几乎不可能将在同一缓冲区上工作的多个工作程序的操作与一个公共数组同步。解决优化问题
使用Atomics全局变量,其方法有三个主要用途。第一个用例:同步。原子方法可用于与其他工作人员同步。例如,以下两个操作允许您读取和写入数据,并且不会被编译器重新排序: Atomics.load (TypedArray <T>, index) : T Atomics.store (TypedArray <T>, index, value: T) : T
想法是使用普通操作读取和写入大多数数据,而Atomics操作(加载,存储和其他操作)确保读取和写入是安全的。通常,您将使用基于Atomics的自己的同步机制,例如锁。这是一个非常简单的示例,由于使用了Atomics,它始终可以正常工作(我跳过了sharedArray的设置):
第二个用例:等待通知。使用while循环来等待通知不是很有效,因此Atomics具有帮助性的操作:Atomics.wait(Int32Array,索引,值,超时)和Atomics.wake(Int32Array,索引,计数)。第三种用例:原子运算某些原子运算可以算术且不能同时中断,这有助于同步。例如:
Atomics.add (TypedArray <T>, index, value) : T
粗略地说,该操作执行:index + = value;值撕裂的问题。共享内存的另一个问题是值撕裂(垃圾):读取时,您会看到一个中间值-新值被写入内存之前既没有值,也没有新值。规范的“无撕裂阅读”部分规定,当且仅当:- 读取和写入都通过类型化数组(而非DataView)进行。
- 这两个类型化的数组都与它们的共享数组缓冲区对齐:sharedArray.byteOffset%sharedArray.BYTES_PER_ELEMENT === 0
- 两种类型的数组每个元素的字节数相同。
换句话说,当通过以下方式访问共享数组的相同缓冲区时,残缺值是一个问题:- 一个或多个DateViews;
- 有一个或多个未对齐的类型化数组;
- 具有不同大小的元素的类型化数组;
为避免这些情况下的值出现差异,请使用Atomics或sync。使用中的共享数组缓冲区
共享数组缓冲区和JavaScript语义,用于执行未决函数。 JavaScript具有所谓的“完成前”执行语义:每个函数都可以期望它在完成之前不会被另一个线程中断。函数成为事务并可以执行完整的算法,而没有人看到处于中间状态的数据。共享数组缓冲区中断了完成周期(RTC):函数执行过程中,另一个线程可以更改函数正在处理的数据。但是,该代码完全控制是否发生这种RTC违规:如果它不使用共享数组缓冲区,则是安全的。这大致类似于异步函数违反RTC的方式。在那里,您可以使用await关键字启用锁定操作。共享数组缓冲区允许emscripten在asm.js中编译pthread。引用emscripten文档页面:[En] [允许共享阵列缓冲区] Emscripten应用程序可在Web Worker之间共享主内存堆。这与低级原子和futex支持的原语一起使Emscripten能够实现对Pthreads(POSIX线程)API的支持。[Ru] [共享阵列缓冲区允许] Emscripten应用程序在Web Worker之间共享一堆主内存。除了底层原子原语和futex支持之外,Emscripten还支持Pthreads API(POSIX线程)。也就是说,您可以在asm.js中编译多线程C和C ++代码。关于如何最好地在WebAssembly中使用多线程的讨论正在进行中。鉴于Web工作人员相对较重,WebAssembly可能会引入轻量级线程。您还可以看到主题正在走向WebAssembly的未来。交换除整数以外的数据
目前,只能使用整数数组(最长32位)。这意味着共享其他类型数据的唯一方法是将它们编码为整数。可能有用的工具包括:- TextEncoder和TextDecoder:前者将字符串转换为Uint8Array实例,后者则相反。
- stringview.js: , . .
- FlatJS: JavaScript (, ) (ArrayBuffer SharedArrayBuffer). JavaScript + FlatJS JavaScript. JavaScript (TypeScript . .) .
- TurboScript: JavaScript- . asm.js WebAssembly.
最后,可能会出现其他的更高级别的数据交换机制。实验将继续找出这些机制的外观。使用共享数组缓冲区的代码能以多快的速度工作?Lars T. Hansen编写了Mandelbrot算法的两种实现(如他的文章“ JavaScript的新并行基元的味道 ”所述,它是使用多个Web工作者的顺序版本和并行版本。最多4个Web工作者,因此处理器核心,加速几乎呈线性增长,从每秒6.9帧(1个Web工作者)到每秒25.4帧(4个Web工作者)。更多的Web工作者带来了额外的生产率提高,但效率却有所提高。Hansen指出,加速是令人印象深刻的,但是并行工作是由于代码更加复杂。有关共享阵列缓冲区和支持技术的其他信息:其他JavaScript并发技术:请参阅官方规范以及MDN Web文档中的Atomics Object 。9.异步功能
使用AsyncFunction构造函数创建Async函数
AsyncFunction构造函数创建一个新的异步函数对象。在JavaScript中,任何异步函数实际上都是AsyncFunction对象。请注意,AsyncFunction不是全局对象。可以通过执行以下代码获得它。 Object.getPrototypeOf(async function(){}).constructor
句法 new AsyncFunction([arg1[, arg2[, ...argN]],] functionBody)
arg1,arg2,... argN-函数用作形式参数名称的名称。每个名称必须是与有效的JavaScript标识符匹配的字符串,或此类逗号分隔的字符串的列表;例如,“ x”,“ theValue”或“ a,b”。functionBody-一个字符串,其中包含JavaScript源代码中的函数定义。使用AsyncFunction构造函数创建的异步函数对象将在函数创建时进行解析。与使用异步函数表达式声明异步函数并在代码内部调用异步函数相比,这效率较低,因为此类函数会与其余代码一起解析。传递给函数的所有参数按照传递顺序被视为创建的函数中的参数标识符的名称。将AsyncFunction构造函数作为函数调用(不使用new运算符)与将其作为构造函数调用具有相同的效果。使用AsyncFunction构造函数创建的异步函数不会使创建它们的上下文短路。它们始终在全局范围内创建。当他们开始时,他们将只能访问其局部变量和全局变量,但无权访问调用AsyncFunction构造函数的作用域。这与将eval与异步函数的代码一起使用不同。使用AsyncFunction构造函数创建异步函数的示例 function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor var a = new AsyncFunction('a', 'b', 'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);'); a(10, 20).then(v => { console.log(v);
异步函数声明
异步函数声明定义了一个异步函数,该函数返回一个AsyncFunction对象。您还可以使用异步函数表达式定义异步函数。句法 async function name([param[, param[, ... param]]]) {
name-函数的名称。param-要传递给函数的参数名称。语句-包含函数主体的表达式。调用之后,异步函数返回Promise。接收到结果后,Promise完成,返回接收到的值。当异步函数引发异常时,Promise将失败,并抛出一个值。异步函数可以包含一个等待表达式,该表达式暂停异步函数的执行并等待来自所传递的Promise的响应,然后恢复异步函数并返回接收到的值。await关键字仅在异步函数中有效。在另一种情况下,您将收到一个SyntaxError错误。异步/等待功能的目的是同步简化promise的使用,并在Promises组上重现一些操作。就像承诺就像结构化回调一样,异步/等待就像生成器和承诺的组合。例子 function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } async function add1(x) { const a = await resolveAfter2Seconds(20); const b = await resolveAfter2Seconds(30); return x + a + b; } add1(10).then(v => { console.log(v);
异步的作用是什么?
您可以在一个.js文件中编写JS程序,但是您的代码很可能会分成几部分。现在仅执行其中一部分,其余部分将在以后执行。函数是将程序分为多个部分的最常用的技术。对于大多数初次使用JS的开发人员来说,主要问题是对现在以后不会发生的事情缺乏了解。换句话说,根据定义,现在无法完成的任务将异步结束。而且,我们将不会拥有假定的程序的阻塞行为。 (You-Dont-Know-JS /异步与性能,Jake Archibald)。
这是什么错误?在我们从请求接收数据之前执行console.log()。从现在开始到以后“等待”的明显决定是使用回调: ajax( "http://some.url.1", function myCallbackFunction(data){ console.log( data );
考虑过早解决同步代码执行的各种方法。我们有3个函数getUser,getPosts,getComments。 const { getUser, getPosts, getComments } = require('./db'); getUser(1, (error, user) => { if(error) return console.error(error); getPosts(user.id, (error, posts) => { if(error) return console.error(error); getComments(posts[0].id, (error, comment) => { if(error) return console.error(error); console.log(comments); }); }); });
在此示例中,很难不注意到金字塔,金字塔随着增加的新功能而增加。这种编码风格通常称为“ 回调地狱”。这是一种特定模式,可让您控制竞争(异步)请求,从而确保执行顺序。解决嵌套函数问题的部分方法是使用Promise(我在上一篇文章中讨论了Promise ,它删除了它并使代码更整洁。它们还提供了更方便的错误处理方式。但是许多人不喜欢这种语法。 getUser(1) .then(user => getPosts(user,id)) .then(posts => getComments(posts[0].id)) .then(comments => console.log(comments)) .catch(error => console.error(error));
生成器成为Promise的替代方法(我也在上一篇文章中进行了讨论。生成器本身不适合编写异步代码,但是如果将它们与Promise一起使用,我们会得到一些独特的东西-看起来是同步的异步代码。同时,生成器提供了使用try ... catch构造函数的熟悉的错误处理机制只有生成器有一个很大的负号-为了与Promise一起使用,您将需要一个单独的函数来控制生成器的过程答:您可以自己编写此函数,也可以使用第三方库(例如co)。在本示例中,我编写了此类函数的实现。 co(function* () { try { let user = yield getUser(1); let posts = yield getPosts(user.id); let comments = yield getComments(posts[0].id); console.log(comments); } catch (error) { console.log(error); } }); function co(generator) { const iterator = generator(); return new Promise((resolve, reject) => { function run(prev) { const { value, done } = iterator.next(prev); if (done) resolve(value); else if (value instanceof Promise) value.then(run, reject); else run(value); } run(); }); }
使用异步代码的每种方法都有其优点和缺点。回调函数(回调函数)-易于使用,但是随着嵌套函数的增加,可读性开始受到损害。Promises(Promises)-优雅而舒适,但对于初学者来说很难理解。生成器(Generators)-允许您同步编写异步代码,但是它们需要单独的功能,并且生成器的操作机制非常复杂。异步函数是在Promises和Generators的基础上创建的,以便使异步代码的工作变得简单易懂。为了了解什么是异步函数,请考虑以下示例: function getUser(id) { return { id: 1 }; } let user = getUser(1); console.log(user);
现在,如果使函数异步(添加async关键字),则该函数将返回一个Promise,其中包含具有id属性的对象。 async function getUser(id) { return { id: 1 }; } let user = getUser(1); console.log(user);
因此,我们可以说任何异步函数都返回Promis(或者将其应返回的值包装在Promis中)。如果返回给异步函数的值已经是一个Promise,则将不会再次将其转回。为了从诺言中获得价值,我们可以使用then()方法。 async function getUser(id) { return { id: 1 }; } getUser(1) .then(user => console.log(user));
或者,我们可以使用await关键字,稍后将对其进行讨论。让我们回到第一个示例(仅这次,我们将使用real函数发送HTTP请求。 fetch(`https://jsonplaceholder.typicode.com/users/1`) .then(data => data.json()) .then(data => console.log(data));
这就是使用Promise异步代码的样子。但是,如果使用异步函数,我们可以将异步代码编写为同步代码。 async function sendRequest() { let response= await fetch(`https://jsonplaceholder.typicode.com/users/1`); return response.json(); } async function main() { var a = await sendRequest(); console.log(a); } main();
我唯一不喜欢的是异步运算符只能在异步函数中使用。否则,我将不需要使用main()函数。当然,您也可以使用then()方法,但随后的代码将不再是异步的。 async function sendRequest() { let response= await fetch(`https://jsonplaceholder.typicode.com/users/1`); return response.json(); } sendRequest() .then((data) => console.log(data));
最重要的是,我们不使用回调函数从fetch()获取数据。相反,我们使用await关键字,它实际上告诉了运行时:等待fetch()函数执行并将结果写入响应变量。然后使用回调函数,我们说:等待fetch()函数执行并调用回调函数来处理数据。这是使用Promise和异步功能之间的明显区别
await运算符只能在异步函数的主体中使用,并且其操作可以在任何返回promise的函数中使用。为了处理异步函数中的异常,习惯上使用try ... catch构造。 async function sendRequest() { let response = await fetch(`https://jsonplaceholder.typicode.com/users/1`); try { throw new Error("Unexpected error"); return response.json(); } catch(error) { console.log(error);
最后...
请参阅官方规范以及MDN Web文档中的“ 异步函数定义” 。