一个 JSer 的 Dart 学习日志(四):异步编程
source link: https://segmentfault.com/a/1190000040785317
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
本文是“一个 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 的特色(就这两门语言而言)。
Dart 特有的
1. 回调函数的类型
在定义一个函数的时候,回调函数是其参数之一参数,而函数的参数是可以声明类型的,回调函数概莫能外。不过由于函数的要素较多,与其他类型的声明方式不一样——
其他类型:类型关键字在变量名前面:void func(int a, Map<String, dynamic> b){ // Do something }
。
函数类型:函数的类型是Function
,但声明形参类型为函数的语法并不是Function callback
:// callback 第一个参数为 `num` 类型,第二个参数为 `int`类型 // callback 的返回值为 `String` 类型 // 如果这些类型都未显式声明的话,则全部都是 dynamic void func(String callback(num a, int b)){ // Do something }
二. 使用生成器函数
ES6 加入了生成器函数,可以暂时中断执行,并在“唤醒”之后继续执行,这个特性与异步过程相得益彰,因此生成器函数也被用于处理异步过程。
Dart 中也有生成器函数,在概念上与 JS 生成器类似,但在语法和使用方式上有很多不同。
1. 使用 *
和 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
的接口事件。
2. “惰性”求值
- 生成器函数执行遇到
yield
即中止,并返回一个特定对象,调用此对象的特定方法,函数才会继续运行,直到遇到下一个yield
。
1. Dart 生成器有同步与异步之分
- 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 {<pending>} | // Instance of '_Future<dynamic>'
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<int> foo async { return 1; }
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
4. 包装值与错误
- 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 的这个写法无关紧要,只要在面试的时候不要掉链子就行。
6. Promise
有更多丰富的功能
熟悉
Promise
的人不会对Promise.allSettle
、Promise.race
、Promise.any
这些静态方法感到陌生,而这些方法是Future
所不具有的,希望早日能在 Dart 里见到它们。JS 总算扳回一局!
四. async/await
如果你问我最喜欢自
ES6
以来加入的哪个新特性,那毫无疑问是ES2017
带来的async/await
语法和ES2015
带来的解构语法。
而在 Dart 中,async/await
这一神兵利器也没有缺席!
7. 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(); > } | {
1. async
关键字的位置
- 在 JS 中,
async
置于函数声明语句的前面; 在 Dart 中,
async
置于函数参数列表的后面。这个区别在上面的例子中已经有所体现。
TODO: 需要稍微研究下 Dart 构造函数初始化实例变量的时候,
async
放哪里。所以这里总结的位置不一定是对的。
2. 分别返回Promise
和Future
- 在 JS 中,
async
函数返回Promise
实例; - 在 Dart 中,
async
函数返回Future
实例。
两种类的差异在上一节已经阐明(至少作者自己觉得是阐明了),因此不再赘述。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK