途 务 和 等 关键字是基于承诺的语法糖,它们使得异步代码更易于编写和阅读。通过使用它们,异步代码看起来更像是老式同步代码,因此它们非常值得学习。本文为您提供了您需要了解的内容。
途和等基础
在代码中使用 途 /*async*/和等/*await*/ 有两个部分。
途 关键字
首先,我们使用 途
关键字,你把它放在函数声明的前面,把它变成一个异步函数。异步函数是一个知道怎样使用 等
关键字调用异步代码的函数。
尝试运行以下行:
务 你好() { 回 "你好" };
你好();
该函数返回“你好” —— 没什么特别的,对吧?
如果我们将其变成异步函数呢?请尝试以下方法:
途 务 你好() { 回 "你好" };
控制台.日志(你好());
哈。现在调用该函数会返回一个 承诺。这是异步函数的特征之一 —— 它保证函数的返回值为 承诺。
你也可以创建一个异步函数表达式,如下所示:
定 你好 = 途 务() { 回 "你好" };
你可以使用箭头函数:
定 你好 = 途 () => { 回 "你好" };
这些都基本上是一样的。
要实际使用承诺完成时返回的值,我们可以使用.下()
代码块,因为它返回的是 承诺:
你好().下((值) => 控制台.日志(值));
甚至只是简写如
你好().下(控制台.日志);
这就像我们在上一篇文章中看到的那样。
将 途
关键字加到函数申明中,可以告诉它们返回的是 承诺,而不是直接返回值。此外,它避免了同步函数为支持使用 等 带来的任何潜在开销。在函数声明为 途
时,君土脚本引擎会添加必要的处理,以优化你的程序。
等关键字
当 等 关键字与异步函数一起使用时,它的真正优势就变得明显了 —— 事实上, 等 只在异步函数里面才起作用。它可以放在任何异步的、基于 承诺 的函数之前。它会暂停代码在该行上,直到 承诺 完成,然后返回结果值。在暂停的同时,其他正在等待执行的代码就有机会执行了。
您可以在调用任何返回承诺的函数时使用 等,包括网络编程接口函数。
这是一个简单的示例:
途 务 你好() {
回 等 诺.解决("你好");
};
你好().下(控制台.日志);
当然,上面的示例不是很有用,但它确实展示了语法。让我们继续,看一个真实示例。
使用 途/等 重写 承诺 代码
让我们回顾一下我们在上一篇文章中简单的 阿修斯.取 示例:
引 阿修斯 自 '阿修斯';
定 网址 = `https://git.jtu.net.cn/xuexi/shuru/-/raw/master/${编码地址('书')}.json`;
阿修斯.取(网址).下(应答 => {
若(应答.状态 != 200) {
抛 启 错误('读取数据出错:' + 应答.状态);
} 别 {
回 应答.数据
}
}).下(数据 => {
控制台.日志(象谱.串(数据));
}).接(错 => {
控制台.日志('出错了:' + 错);
});
到现在为止,你应该对 承诺 及其工作方式有一个较好的理解。让我们将其转换为使用途 / 等看看它使事情变得简单了多少:
引 阿修斯 自 '阿修斯';
定 网址 = `https://git.jtu.net.cn/xuexi/shuru/-/raw/master/${编码地址('书')}.json`;
途 务 读取数据() {
定 应答 = 等 阿修斯.取(网址);
若(应答.状态 != 200) {
抛 启 错('读取数据出错:' + 应答.状态);
}
定 数据 = 应答.数据;
控制台.日志(象谱.串(数据));
}
读取数据()
.接(错误 => {
控制台.日志('出错了:' + 错误);
});
它使代码简单多了,更容易理解 —— 去除了到处都是 .下()
代码块!
由于 途
关键字将函数转换为 承诺,您可以重构以上代码 —— 使用 承诺 和 等 的混合方式,将函数的后半部分抽取到新代码块中。这样做可以更灵活:
引 阿修斯 自 '阿修斯';
定 网址 = `https://git.jtu.net.cn/xuexi/shuru/-/raw/master/${编码地址('书')}.json`;
途 务 读取数据() {
定 应答 = 等 阿修斯.取(网址);
若(应答.状态 != 200) {
抛 启 错误('读取数据出错:' + 应答.状态);
}
回 应答.数据;
}
读取数据()
.下(数据 => {
控制台.日志(象谱.串(数据));
});
它到底是如何工作的?
您会注意到我们已经将代码封装在函数中,并且我们在 务
关键字之前包含了 途
关键字。这是必要的 –– 您必须创建一个异步函数来定义一个代码块,在其中运行异步代码; 等
只能在异步函数内部工作。
在读取数据()
函数定义中,您可以看到代码与先前的 承诺 版本非常相似,但存在一些差异。不需要附加 .下()
代码块到每个基于承诺方法的结尾,你只需要在方法调用前添加 等 关键字,然后把结果赋给变量。等 关键字使君土脚本运行时暂停于此行,允许其他代码在此期间执行,直到异步函数调用返回其结果。一旦完成,您的代码将继续从下一行开始执行。例如:
定 应答 = 等 阿修斯.取(网址);
解析器会在此行上暂停,直到当服务器返回的响应变得可用时。此时 阿修斯.取()
返回的 承诺 将会完成,返回的 应答
会被赋值给 应答
变量。一旦服务器返回的响应可用,解析器就会移动到下一行。我们将数据
从读取数据()
函数中返回。
这意味着当我们调用读取数据()
函数时,它会返回一个承诺,因此我们可以将.下()
链接到它的末尾,在其中我们把数据显示在控制台上。
你可能已经觉得“这真的很酷!”,你是对的 —— 用更少的.下()
代码块来封装代码,同时它看起来很像同步代码,所以它非常直观。
添加错误处理
如果你想添加错误处理,你有几个选择。
您可以将同步的 试...接
结构和 途/等
一起使用 。此示例扩展了我们上面展示的第一个版本代码:
引 阿修斯 自 '阿修斯';
定 网址 = `https://git.jtu.net.cn/xuexi/shuru/-/raw/master/${编码地址('书')}.json`;
途 务 读取数据() {
试 {
定 应答 = 等 阿修斯.取(网址);
若(应答.状态 != 200) {
抛 启 错误('读取数据出错:' + 应答.状态);
}
控制台.日志(象谱.串(应答.数据));
} 接 (错) {
控制台.日志(错);
}
}
读取数据();
接() {}
代码块会接收一个错误对象 错误 ; 我们现在可以将其记录到控制台,它将向我们提供详细的错误消息,显示错误被抛出的代码中的位置。
如果你想使用我们上面展示的第二个(重构)代码版本,你最好继续混合方式并将 .接()
块链接到 .下()
调用的末尾,就像这样:
引 阿修斯 自 '阿修斯';
定 网址 = `https://git.jtu.net.cn/xuexi/shuru/-/raw/master/${编码地址('书')}.json`;
途 务 读取数据() {
定 应答 = 等 阿修斯.取(网址);
若(应答.状态 != 200) {
抛 启 错误('读取数据出错:' + 应答.状态);
}
回 应答.数据;
}
读取数据().下(数据 => {
控制台.日志(数据);
}).接(错 => {
控制台.日志(错);
});
这是因为 .接()
块将捕获来自异步函数调用和承诺链中的错误。如果您在此处使用了试/接
代码块,则在调用 读取数据()
函数时,您仍可能会收到未处理的错误。
等待诺.全()
途 / 等
建立在 承诺 之上,因此它与承诺提供的所有功能兼容。这包括诺.全()
–– 你完全可以通过调用 等
诺.全()
将所有结果返回到变量中,就像同步代码一样。让我们再次回到上一篇中看到的例子。
将其转换为 途 / 等,现在看起来像这样:
引 阿修斯 自 '阿修斯';
途 务 显示内容() {
定 网址1 = `https://git.jtu.net.cn/xuexi/shuru/-/raw/master/${编码地址('书')}.json`;
定 网址2 = `https://git.jtu.net.cn/xuexi/shuru/-/raw/master/${编码地址('书2')}.json`;
定 网址3 = `https://git.jtu.net.cn/xuexi/shuru/-/raw/master/${编码地址('书3')}.json`;
定 书1 = 阿修斯.取(网址1);
定 书2 = 阿修斯.取(网址2);
定 书3 = 阿修斯.取(网址3);
定 值数组 = 等 诺.全([书1, 书2, 书3]);
定 回应0 = 值数组[0].数据;
定 回应1 = 值数组[1].数据;
定 回应2 = 值数组[2].数据;
控制台.日志(象谱.串(回应0));
控制台.日志(象谱.串(回应1));
控制台.日志(象谱.串(回应2));
}
显示内容().接((错: 错误) => {
控制台.日志('错误:' + 错.信息);
});
请看诺.全()
行:
定 值数组 = 等 诺.全([书1, 书2, 书3]);
在这里,通过使用等
,我们能够在三个承诺的结果都可用的时候,放入数据组
数组中。这看起来非常像同步代码。我们需要将所有代码封装在一个新的异步函数显示内容()
中,尽管没有减少很多代码,但能够将大部分代码从 .下()
代码块移出,使代码得到了简化,更易读。
为了错误处理,我们在 显示内容()
调用中包含了一个 .接()
代码块;这将处理两个函数中出现的错误。
注意: 也可以在异步函数中使用同步 终
代码块代替 .终()
异步代码块,以显示操作如何进行的最终报告。
途/等的缺陷
了解途/等
是非常有用的,但还有一些缺点需要考虑。
途/等
让你的代码看起来是同步的,在某种程度上,也使得它的行为更加地同步。 等
关键字会阻塞其后的代码,直到承诺完成,就像执行同步操作一样。它确实可以允许其他任务在此期间继续运行,但您自己的代码被阻塞。
这意味着您的代码可能会因为大量等
的承诺相继发生而变慢。每个等
都会等待前一个完成,而你实际想要的是所有的这些承诺同时开始处理(就像我们没有使用途/等
时那样)。
有一种模式可以缓解这个问题——通过将 承诺 对象存储在变量中来同时开始它们,然后等待它们全部执行完毕。让我们看一些证明这个概念的例子。
我们有两个可用的例子 —— 慢调用异步和快调用异步。它们都以自定义承诺函数开始,该函数使用设置超时()
调用伪造异步进程:
务 超时承诺(间隔: 数) {
回 启 诺((解决, 驳回) => {
设置超时(务 () {
解决("好了");
}, 间隔);
});
};
然后每个包含一个 时间测试()
异步函数,等待三个 超时承诺()
调用:
途 务 时间测试() {
...
}
每一个都以记录开始时间结束,查看 时间测试()
承诺 需要多长时间才能完成,然后记录结束时间并报告操作总共需要多长时间:
定 开始时间 = 日期.现在();
时间测试().下(() => {
定 结束时间 = 日期.现在();
定 耗时 = 结束时间 - 开始时间;
控制台.日志("使用了" + 耗时 + "毫秒");
})
时间测试()
函数在每种情况下都不同。
在慢调用异步示例中,时间测试()
如下所示:
途 务 时间测试() {
等 超时承诺(3000);
等 超时承诺(3000);
等 超时承诺(3000);
}
在这里,我们直接等待所有三个超时承诺()
调用,使每个调用3秒钟。后续的每一个都被迫等到最后一个完成 – 如果你运行第一个例子,你会看到弹出框报告的总运行时间大约为9秒。
在快调用异步示例中,时间测试()
如下所示:
途 务 时间测试() {
定 诺1 = 超时承诺(3000);
定 诺2 = 超时承诺(3000);
定 诺3 = 超时承诺(3000);
等 诺1;
等 诺2;
等 诺3;
}
在这里,我们将三个承诺对象存储在变量中,这样可以同时启动它们关联的进程。
接下来,我们等待他们的结果 – 因为承诺都在基本上同时开始处理,承诺将同时完成;当您运行第二个示例时,您将看到弹出框报告总运行时间仅超过3秒!
您必须仔细测试您的代码,并在性能开始受损时牢记这一点。
另一个小小的不便是你必须将等待执行的承诺封装在异步函数中。
途/等 的类方法
最后值得一提的是,我们可以在类/对象方法前面添加途
,以使它们返回承诺,并等
它们内部的承诺。查看使用异步方法的修改版本:
类 人 {
姓名: { 姓: 文, 名: 文 };
年龄: 数;
性别: 文;
兴趣: 文[];
构(姓: 文, 名: 文, 年龄: 数, 性别: 文, 兴趣: 文[]) {
此.姓名 = { 姓, 名 };
此.年龄 = 年龄;
此.性别 = 性别;
此.兴趣 = 兴趣;
}
描述() {
控制台.日志(此.姓名.姓 + 此.姓名.名 + '有' + 此.年龄 + '岁。他喜欢' + 此.兴趣[0] + ' 和 ' + 此.兴趣[1] + '。');
}
途 问候() {
回 等 诺.解决("你好, 我是" + 此.姓名.姓 + 此.姓名.名);
}
}
定 人1 = 启 人("张", "三", 32, '男', ['编程', '乒乓球']);
第一个实例方法可以使用如下:
人1.问候().下(控制台.日志);
总结
途/等 提供了一种很好的,简化的方法来编写更易于阅读和维护的异步代码。