1+1=10

记记笔记,放松一下...

JavaScript学习笔记(七)

接前面的:

继续学习JavaScript。通过和Python对比方式:

  • 看看JavaScript中的4种函数
  • 对比看看生成器函数,基本无差别
  • 对比看看异步函数,和Python类似,但底层差异很大
  • 对比看看异步生成器函数

Python to JavaScript?

Python 协程小记(一)中,我们了解Python中的5种函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import asyncio
import types

def func1():
    print('function')

def func2():
    print('generator')
    yield

async def func3():
    print('coroutine')
    await asyncio.sleep(1)

async def func4():
    print('async generator')
    yield

@types.coroutine
def func5():
    print('generator-based coroutine')
    yield

# 以上函数调用方式各异    
async def main():
    func1()
    f2 = func2(); next(f2)
    await func3()
    f4 = func4(); await anext(f4)
    await func5()

asyncio.run(main())

那么在JavaScript中,和python能对应上吗?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function func1() {
    console.log('function');
  }

function* func2() {
    console.log('generator');
    yield;
}

async function func3() {
    console.log('coroutine');
    await new Promise((resolve) => setTimeout(resolve, 1000));
}

async function* func4() {
    console.log('async generator');
    yield;
}


// 执行方式各异
func1();
const f2 = func2();
f2.next();
await func3();
const f4 = func4();
await f4.next();

看起来不错,除了没有Python自己折腾的@types.coroutine,基本一一对应。

不过:

  • JavaScript中的 async 是基于Promise的语法糖,Python中的 async 不是。
  • JavaScript中的 生成器使用function*与普通函数区分,Python只依赖其内部是否有yield。

JavaScript四种函数

这样更直观一些:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function func1() {
}

function* func2() {
    yield;
}

async function func3() {
    await new Promise((resolve) => setTimeout(resolve, 1000));
}

async function* func4() {
    yield;
}

console.log(func1) // [Function: func1]
console.log(func2) // [GeneratorFunction: func2]
console.log(func3) // [AsyncFunction: func3]
console.log(func4) // [AsyncGeneratorFunction: func4]

或者这样

1
2
3
4
5
6
7
8
9
function func1() {}
function* func2() {}
async function func3() {}
async function* func4() {}

console.log(func1) // [Function: func1]
console.log(func2) // [GeneratorFunction: func2]
console.log(func3) // [AsyncFunction: func3]
console.log(func4) // [AsyncGeneratorFunction: func4]

这四种函数对象各不相同:

  • 函数
  • 生成器函数
  • 异步函数
  • 异步生成器函数

它们的返回值,也可以简单看一下:

  • 异步函数的返回值是 Promise!(不同于Python下称呼的 coroutine)
1
2
3
4
5
6
7
8
9
function func1() {}
function* func2() {}
async function func3() {}
async function* func4() {}

console.log(func1()) // undefined
console.log(func2()) // Object [Generator] {}
console.log(func3()) // Promise { undefined }
console.log(func4()) // Object [AsyncGenerator] {}

注:

  • yield 在生成器或异步生成器函数内使用
  • await 在异步函数和异步生成器函数内使用。(严格模式下或模块中,还可以用顶级 await)

在标准化之前,生成器也有不用function*而不是直接判定function内有无yield的用法。在标准化之前,也有是否使用 async,还是只判定的await的提案。

生成器函数

这个也可以和Python生成器与yield进行对比。

Python vs Javascript 例子

Python下生成器函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def my_generator():
    yield 1
    yield 2
    yield 3

# use the generator
gen = my_generator()
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3

JavaScript生成器函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function* myGenerator() {
    yield 1;
    yield 2;
    yield 3;
  }

  // use the generator
  const gen = myGenerator();
  console.log(gen.next().value); // 1
  console.log(gen.next().value); // 2
  console.log(gen.next().value); // 3

用起来很像。

生成器函数 与 生成器

  • function*定义的是生成器函数(
  • 函数返回值是 生成器(Generator)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function* myGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

// use the generator
const gen = myGenerator();
console.log(myGenerator); // [GeneratorFunction: myGenerator]
console.log(gen); // Object [Generator] {}
  • 生成器实现了可迭代协议(iterable protocol)与迭代器协议(iterator protocol)。
1
2
3
4
5
// use the generator
const gen = myGenerator();
for (const value of gen) {
    console.log(value);
}

对比 Python

对于生成器进行迭代,JavaScript下的for... of和Python下的for in用法也很类似:

1
2
3
4
5
6
7
8
9
def my_generator():
    yield 1
    yield 2
    yield 3

# use the generator
gen = my_generator()
for value in gen:
    print(value)

yield

  • javascript下区分 yieldyield*,后者用于代理其他迭代器:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function* func1() {
  yield "1+1=10";
}

function* func2() {
  yield* func1();
}

const iterator = func2();

console.log(iterator.next().value); // 1+1=10
  • 就如同 python下区分 yieldyield from一样
1
2
3
4
5
6
7
8
9
def func1():
    yield "1+1=10"

def func2():
    yield from func1()

iterator = func2()

print(next(iterator))  # 1+1=10

可迭代 与 迭代器

  • 可迭代对象需要实现 @@iterator 属性,该属性用于返回迭代器。
  • 迭代器需要实现 next()方法,该方法可以接受无参数或一个参数(可类比Python下的send函数)

ECMAScript 引入 Symbol后,可以使用[Symbol.iterator] 替代 字符串@@iterator写法。

前面生成器函数可用于生成可迭代对象。

不用生成器函数,也可以手动实现可迭代协议:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 手动实现可迭代协议的对象
const myIterable = {
  data: [1, 2, 3],
  [Symbol.iterator]: function() {
    let index = 0;
    return {
      next: () => {
        if (index < this.data.length) {
          return { value: this.data[index++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

// use for...of
for (const value of myIterable) {
  console.log(value);
}

不直接创建对象,用class是否更面向对象一些?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MyIterable {
  constructor() {
    this.data = [1, 2, 3];
  }

  // 实现可迭代协议的方法
  [Symbol.iterator]() {
    let index = 0;

    return {
      next: () => {
        if (index < this.data.length) {
          return { value: this.data[index++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
}

// use for...of
const myIterable = new MyIterable();
for (const value of myIterable) {
  console.log(value);
}

还能这么写?:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const myIterable = {};

myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

// use for...of
for (const value of myIterable) {
  console.log(value);
}

异步函数

async的例子很简单

例子

1
2
3
4
5
6
7
8
async function func() {
    await Promise.resolve('1+1=10');
    return '1+1=2';
}

console.log(func) // [AsyncFunction: func]
console.log(func()) // Promise { <pending> }
console.log(await func()) // 1+1=2

但,不妨先和Python对比一下,

异步函数、协程函数

- JavaScript Python
async 异步函数,async function f(){} 协程函数, async def f():
函数返回值 Promise对象,Thenable 协程对象,Awaitable
使用方式 await 关键字用于等待异步操作完成 await 关键字用于等待协程完成

再试着,放个图看看:

javascript-promise-vs-python-coroutine

不同于JavaScript和Python在生成器方面非常类似,它们在Promise异步函数和协程函数返回值上,差别还是很大的。但是形式上都可以配合 await 使用。

JS Promise 与 Python Future?

Javascript中的Promise 与 Python 中的 asyncio.Future 在概念上,有些类似。

在Python中,可以把一个协程转换成 Future,而后使用 await 来等待它完成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import asyncio

async def async_operation():
    await asyncio.sleep(1)
    return "1+1=10"

async def main():
    # convert async operation to future
    future = asyncio.ensure_future(async_operation())
    result = await future
    print(result)

asyncio.run(main())

对应 Javascript代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
async function asyncOperation() {
    await new Promise(resolve => setTimeout(resolve, 1000));
    return "1+1=10";
}

async function main() {
    const promise = asyncOperation();
    const result = await promise;
    console.log(result);
}

main();

注: * JavaScript中,Promise是由异步函数返回的一个对象,它表示操作的当前状态。在将Promise返回给调用者的时候,操作通常尚未完成,但是Promise对象提供了方法来响应其最终成功或失败的场景。 * Python中,asyncio.Futureasyncio 模块中用于表示异步操作结果的类。它表示一个可能尚未完成的异步操作,可以在将来的某个时刻完成,并且在完成后可以包含一个结果或一个异常。

二者都提供了方式来设置目标值,或者没有目标值时设置错误原因(异常)。

await

在 JavaScript 中,await 关键字用于等待一个 Promise 对象的完成。当使用 await 时,会发生:

  1. 暂停执行:await 遇到时,它会暂停当前 async 函数的执行,但不会阻塞整个程序或浏览器页面。
  2. 让出执行线程: await 会让出执行线程,使得事件循环可以处理其他任务。这是 JavaScript 中单线程执行的一个关键特性,允许异步操作不会阻塞程序的其他部分。
  3. 等待 Promise 解决(完成): await 表达式后面通常是一个返回 Promise 对象的异步操作。它等待这个 Promise 对象的状态变为 resolved(解决)或 rejected(拒绝)。
  4. 恢复执行: 一旦 Promise 对象被解决,await 表达式返回解决值,并且 async 函数继续执行。

异步迭代器

字面理解,就是异步函数和迭代器的复合了,例子挺直观的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
async function* asyncGenerator() {
    yield new Promise(resolve => setTimeout(() => resolve("1+1=10"), 1000));
    yield new Promise(resolve => setTimeout(() => resolve("1+1=2"), 1000));
    yield new Promise(resolve => setTimeout(() => resolve("1+1!=3"), 1000));
}

const asyncIterable = asyncGenerator();
for await (const value of asyncIterable) {
    console.log(value);
}

// output:
// 1+1=10
// 1+1=2
// 1+1!=3

用Python改写的话,也不复杂:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import asyncio

async def async_generator():
    await asyncio.sleep(1)
    yield "1+1=10"

    await asyncio.sleep(1)
    yield "1+1=2"

    await asyncio.sleep(1)
    yield "1+1!=3"

async def main():
    async_iterable = async_generator()

    async for value in async_iterable:
        print(value)

asyncio.run(main())

# output:
# 1+1=10
# 1+1=2
# 1+1!=3

二者还是挺像的,不过:

  • JavaScript使用for await ... of
  • Python使用 async for ... in

参考

  • https://262.ecma-international.org/8.0/#sec-async-function-definitions
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
  • QTBUG-58620 Add async/await support to V4
  • https://dev.to/3shain/promise-coroutine-548i