一个|一个 JSer 的 Dart 学习日志(四)(异步编程)
本文是“一个 JSer 的 Dart 学习日志”系列的第四篇,本系列文章主要以挖掘 JS 与 Dart 异同点的方式,在复习和巩固 JS 的同时平稳地过渡到 Dart 语言。一. 使用回调函数 共同之处
鉴于作者尚属 Dart 初学者,所以认识可能会比较肤浅和片面,如您慧眼识虫,希望不吝指正。
如无特殊说明,本文中 JS 包含了自 ES5 至 ES2021 的全部特性, Dart 版本则为 2.0 以上版本。
Google 原本是将 Dart 作为 JS 的继任者来开发的,因此在设计上借鉴了许多 JS 的特性,例如事件驱动和单线程,这使得它们的异步编程写法也十分相似。
- 无论是 JS 还是 Dart ,都秉承着“一切皆对象”的理念,函数概莫能外,将一个函数作为参数传递,以期在适宜时机调用,是最简单的异步编程方案,此函数即回调函数。
> /* ******************** Both JS and Dart ******************** */ > var func = (param) => param + 1; > var caller = (callback) => callback(1);
- Dart 的异步过程与 JS 不一样——它是阻塞的,仅靠回调函数并不能解决问题,因此回调函数风格的异步接口是 JS 的特色(就这两门语言而言)。
- 在定义一个函数的时候,回调函数是其参数之一参数,而函数的参数是可以声明类型的,回调函数概莫能外。不过由于函数的要素较多,与其他类型的声明方式不一样——
其他类型:类型关键字在变量名前面:
void func(int a, Map b){ // Do something }
。
函数类型:函数的类型是Function
,但声明形参类型为函数的语法并不是Function callback
:
// callback 第一个参数为 `num` 类型,第二个参数为 `int`类型 // callback 的返回值为 `String` 类型 // 如果这些类型都未显式声明的话,则全部都是 dynamic void func(String callback(num a, int b)){ // Do something }
。
ES6 加入了生成器函数,可以暂时中断执行,并在“唤醒”之后继续执行,这个特性与异步过程相得益彰,因此生成器函数也被用于处理异步过程。共同之处 1. 使用
Dart 中也有生成器函数,在概念上与 JS 生成器类似,但在语法和使用方式上有很多不同。
*
和 yeild
- 使用
*
声明一个生成器函数,使用yield
关键字暂停函数,并生成值。
> /* JS */| /* Dart */ > function* gen(max) {| gen(int max) sync* { >var state = 0; |var state = 0; >while(state < max) {|while(state < max) { >yield state ++; |yeild state ++; >}|} > }| } > const reader = gen(6); | final reader = gen(6); > console.log(reader.next().value); | print('${render.elementAt(0)}'); > // 0| // 0
生成器函数适用于一些重复触发调用的场景,例如
WebSocket
的接口事件。
- 生成器函数执行遇到
yield
即中止,并返回一个特定对象,调用此对象的特定方法,函数才会继续运行,直到遇到下一个yield
。
- JS 只有一种生成器,返回的可迭代对象都是
Generator
; - Dart 的生成器分为同步(
sync
)与异步(async
),同步生成器函数返回Iterator
实例,异步生成器返回Stream
实例。
Future
VS Promise
共同之处
1. 包装 async
函数的返回值虽然异步编程的历史还算悠久,但异步函数却是一个年轻的概念,所以在编程语言中引入异步函数的第一个问题就是: 如何将异步函数嵌入到同步的逻辑流里?对于这个问题,JS 给出的答案是
Promise
,相应地,Dart 的答案是 Future
:> /*JS*/|// Dart
> async function asyncFunc() {| asyncFunc() async {
>return await 'yes';
|return await 'yes';
> }| }
> console.log(asyncFunc());
| print(asyncFunc());
> // Promise {}| // Instance of '_Future'
。
2.
.then
方法和链式调用两种方案都使用
.then
语法订阅异步过程的最终结果:>/* JS */|// Dart
> asyncFunc().then(| asyncFunc().then(
>(res) =>|(res) =>
>console.log(`Got ${res}`)|print('Got $res')
> )| )
> // Got yes| // Got yes
。
并且,
.then
方法也会返回一个新的Promise/Future
,订阅此返回值可以获得回调函数的返回值(或回调函数返回的Promise/Future
包装的值):> /*JS*/| // Dart
> async function plus1(v = 0) {| int plus1(int v) async {
>return await v + 1;
|return await v + 1;
> }| }
> function plus2(v = 0) {| int plus2(int v) {
>return v + 2;
|return v + 2;
> }| }
> plus1().then(plus1)| plus1().then(plus1)
>.then(plus2).then(console.log);
|.then(plus2).then(print);
> // 4| // 4
。
不同之处 1. Dart 类型标注
在本系列文章中,Dart 的这个特点算是老生常谈了,Dart
中使用泛型语法约束Future
及其所包装的值的类型:
Future foo async {
return 1;
}
【一个|一个 JSer 的 Dart 学习日志(四)(异步编程)】。
2.
Promise.all
vs Future.wait
这个一时不知道该算共同点还是不同点,因为语法完全一致,仅仅是关键字不同而已:
> /*JS*/| // Dart
> Promise.all([| Future.wait([
>plus1(),|plus1(),
>plus1()|plus1()
> ]).then(| ]).then(
>() => console.log('All done');
|() => print('All done');
> );
| );
。
3. 构造函数的参数不同
传入的函数参数形式不一样
二者都需要传入一个函数,但是这个函数的形式不太一样。
Promise
的excutor
有两个位置参数:resolve
和reject
。Promise
所“包装”的值即resolve
函数的返回值;Future
的computation
函数则没有参数,Future
所包装的正是computation
的返回值。
> /*JS*/| // Dart > const a = new Promise(| final a = /*new*/ Future( >(resolve, reject) => resolve(1)|() => 1 > ); | ); > console.log(await a); | print(await a); > // 1| // 1
computation
默认异步执行
Promise
的excutor
用来初始化Promise
,并且 JS 的异步过程不会阻塞,所以是同步执行的;Future
的computation
直接用来获取值,是异步执行的:
> /*JS*/| // Dart > var mut = 0; | var mut = 0; > const a = new Promise(| final a = /*new*/ Future( >function (resolve, reject) {|() { >mut++; |mut++; >resolve(1); |return 1; >}|} > ); | ); > console.log(mut); | print(mut); > // 1| // 0
;
- 如果想同步执行
computation
,应使用命名构造函数Future.sync
:
int mut = 0; final a = Future.sync((){ mut++; return mut; }); print(mut); // 1
。
- JS 使用
Promise.resolve(value)
将value
包装在一个Promise
中,用Promise.reject(error)
包装错误error
; - Dart 的
Future.value(value)
和Future.error(error)
分别实现上述功能。
其实我不知道这两种包装有什么用。5.
Future
承担了更多异步编程的任务Future.delayed
VS window.setTimeout
- JS 使用顶层对象提供的
setTimeout
接口注册延时任务,这是一个回调风格的接口; - Dart 使用命名构造函数
Future.delayed
注册延时任务:
> /*JS*/| // Dart > var task = setTimeout(| var task = Future.delayed( >() => {|Duration(milliseconds: 100), >console.log('done'); |() { >},|print('done'); >100|} > }; | };
。
Dart 使用
Duration
类来构造时间差,比 JS 默认的毫秒数要直观得多(但是写起来多少有点麻烦,不知道有没有语法糖)。
Future.microstack
VS Promise.resolve().then
- JS 中注册微任务最便捷的方案是
Promise.resolve().then
,(当然,前提是使用运行时提供的Promise
或者靠谱的polyfill
方案),虽然“便捷”,但毕竟只是一种 trick; - 而 Dart 提供了专门的接口
Future.microtask
来注册微任务:
> /*JS*/| // Dart > function register(task){| register(task){ >Promise.resolve.then(|Future.microtask( >task|task >); |); > }| }
。
好在绝大多数情况下,普通的开发者不需要开发者自己调度任务优先级,因此 JS 的这个写法无关紧要,只要在面试的时候不要掉链子就行。
Promise
有更多丰富的功能- 熟悉
Promise
的人不会对Promise.allSettle
、Promise.race
、Promise.any
这些静态方法感到陌生,而这些方法是Future
所不具有的,希望早日能在 Dart 里见到它们。
JS 总算扳回一局!
async/await
如果你问我最喜欢自7.ES6
以来加入的哪个新特性,那毫无疑问是ES2017
带来的async/await
语法和ES2015
带来的解构语法。
而在 Dart 中,async/await
这一神兵利器也没有缺席!
Future
是dart:async
包提供的功能- 如欲使用
Future
(以及),应当先引入dart:async
包。
然而在 Dartpad 中不引入也可以使用。
- Talk is cheap, here is the code:
> /*JS*/| // Dart > async function foo(){| foo () async { >return await asyncFunc(); |return await asyncFunc(); > }| {
.
async
关键字的位置- 在 JS 中,
async
置于函数声明语句的前面; - 在 Dart 中,
async
置于函数参数列表的后面。
这个区别在上面的例子中已经有所体现。
TODO: 需要稍微研究下 Dart 构造函数初始化实例变量的时候,
async
放哪里。所以这里总结的位置不一定是对的。
2. 分别返回Promise
和Future
- 在 JS 中,
async
函数返回Promise
实例; - 在 Dart 中,
async
函数返回Future
实例。
两种类的差异在上一节已经阐明(至少作者自己觉得是阐明了),因此不再赘述。
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量