4

一个 JSer 的 Dart 学习日志(四):异步编程

 2 years ago
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.
neoserver,ios ssh client

本文是“一个 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. 构造函数的参数不同

传入的函数参数形式不一样

二者都需要传入一个函数,但是这个函数的形式不太一样。

  • Promiseexcutor 有两个位置参数:resolverejectPromise所“包装”的值即resolve函数的返回值;
  • Futurecomputation 函数则没有参数,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 默认异步执行

  • Promiseexcutor 用来初始化 Promise,并且 JS 的异步过程不会阻塞,所以是同步执行的;
  • Futurecomputation 直接用来获取值,是异步执行的:

    > /*  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.allSettlePromise.racePromise.any 这些静态方法感到陌生,而这些方法是 Future 所不具有的,希望早日能在 Dart 里见到它们。

    JS 总算扳回一局!

四. async/await

如果你问我最喜欢自ES6以来加入的哪个新特性,那毫无疑问是ES2017带来的async/await语法和ES2015带来的解构语法。
而在 Dart 中,async/await这一神兵利器也没有缺席!

7. Futuredart: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. 分别返回PromiseFuture

  • 在 JS 中,async函数返回Promise实例;
  • 在 Dart 中,async函数返回Future实例。

两种类的差异在上一节已经阐明(至少作者自己觉得是阐明了),因此不再赘述。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK