首页 > 代码库 > JavaScript 最新特性实现的三大黑科技

JavaScript 最新特性实现的三大黑科技

有时候,我们希望批量执行一组异步任务,但是不是并行,而是依次执行,这组任务是动态的,在一个数组里,当然我们可以用 for 循环然后一个一个 await 执行,但是还有另外一种方式:

JS Bin on jsbin.com

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function taskReducer(promise, action){
  let res = await promise;
  return action(res);
}
 
function sleep(ms){
  return new Promise(resolve => setTimeout(resolve, ms));
}
 
async function asyncTask(i){
  await sleep(500);
  console.log(`task ${i} done`);
  return ++i;
}
 
 
[asyncTask, asyncTask, asyncTask].reduce(taskReducer, 0);

在上面的例子里,我们定义了一个 taskReducer:

 
 
1
2
3
4
async function taskReducer(promise, action){
  let res = await promise;
  return action(res);
}

这个 reducer 的两个参数是 promise 和 action,promise 是代表当前任务的 promise,而 action 是下一个要执行的任务。我们可以 await 当前 promise 执行当前任务,然后将执行结果传给下一个 action 就可以了。

这样我们可以调用:

 
 
1
[task1, task2, task3, ...].reduce(taskReducer, init);

不管这些任务是同步还是异步都可以被依次执行。需要注意的是,每一个任务的返回值将是下一个任务的输入 promise 或者 value。

generator 与 async/await 一同使用

将上面的代码进一步扩展,我们发现,它可以支持 generator 与 async/await 一同使用:

JS Bin on jsbin.com

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function reducer(promise, action){
  let res = await promise;
  return action(res);
}
 
function tick(i){
  console.log(i);
  return new Promise(resolve => setTimeout(()=>resolve(++i), 1000));
}
 
function continuous(...functors){
  return async function(input){
    return await functors.reduce(reducer, input)
  }
}
 
function * timing(count = 5){
  for(let i = 0; i yield tick;
  }
}
 
continuous(...timing(10))(0);

在上面的例子里,我们定义了一个计时 tick 函数,我们通过 timing 来连续调用它,而 timing 是一个 generator,计时器显然是异步函数,然而我们可以:

 
 
1
continuous(...timing(10))(0);

而这里的 continuous 其实就是前面的 reduce 的封装。

使用 Proxy 实现 PHP 中的常用“魔术方法”

PHP 中有 __get 、 __set 和 __call 三个强大的魔术方法,可以实现对不存在的属性的读写和方法调用。在新的 ES 标准中添加了 Proxy 类,它可以构造 Proxy 对象,用来“重载”对象的属性和方法读写,从而实现类似于 PHP 的魔术方法:

JS Bin on jsbin.com

 
 
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function Magical(Class){
  return function(...args){
    return new Proxy(new Class(...args), {
      get: function(target, key, receiver) {
        if(typeof target[key] !== ‘undefined‘){
          return target[key];
        }
 
        if (typeof target.__get === ‘function‘){
          return target.__get(key);
        } else if (typeof target.__call === ‘function‘) {
          return function(...args){
            target.__call.apply(this, [key, ...args]);
          };
        }
      },
      set: function(target, key, value, receiver){
        if(typeof target[key] !== ‘undefined‘){
          target[key] = value;
          return;
        }
 
        if (typeof target.__set === ‘function‘){
          target.__set(key, value);
          return;
        }
      }
    });
  }
}
 
class Foo{
  __set(key, value){
    this[key] = value * 2;
  }
  __get(key){
    return this.b;
  }
  __call(key, ...args){
    console.log(`call method ${key} with ${args}`);
  }
  b(...args){
    console.log(`b exists: ${args}`);
  }
}
 
Foo = Magical(Foo);
 
var f = new Foo();
f.b(1,2,3);
f.a(4,5,6);
f.c = 3;
console.log(f.c);

上面的例子里,我们在对象构造的时候,分别“代理”对象实例的属性 get 和 set 方法,如果对象上已存在某个属性或方法,代理直接返回或操作该属性。否则,判断对象上是否有 __get、__set 或者 __call 方法,有的话,做相应的处理。

这里我们使用装饰器模式,定义了一个 Magical 装饰器函数,让它来处理希望使用 Magical 的类。

等到 ES Decorators 标准化了之后,我们就可以使用更加优雅的写法了:

 
 
1
2
3
4
5
6
@magical
class Foo {
    __call(key, ...args){
        ...
    }
}

有时候,我们希望批量执行一组异步任务,但是不是并行,而是依次执行,这组任务是动态的,在一个数组里,当然我们可以用 for 循环然后一个一个 await 执行,但是还有另外一种方式:

JS Bin on jsbin.com

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function taskReducer(promise, action){
  let res = await promise;
  return action(res);
}
 
function sleep(ms){
  return new Promise(resolve => setTimeout(resolve, ms));
}
 
async function asyncTask(i){
  await sleep(500);
  console.log(`task ${i} done`);
  return ++i;
}
 
 
[asyncTask, asyncTask, asyncTask].reduce(taskReducer, 0);

在上面的例子里,我们定义了一个 taskReducer:

 
 
1
2
3
4
async function taskReducer(promise, action){
  let res = await promise;
  return action(res);
}

这个 reducer 的两个参数是 promise 和 action,promise 是代表当前任务的 promise,而 action 是下一个要执行的任务。我们可以 await 当前 promise 执行当前任务,然后将执行结果传给下一个 action 就可以了。

这样我们可以调用:

 
 
1
[task1, task2, task3, ...].reduce(taskReducer, init);

不管这些任务是同步还是异步都可以被依次执行。需要注意的是,每一个任务的返回值将是下一个任务的输入 promise 或者 value。

generator 与 async/await 一同使用

将上面的代码进一步扩展,我们发现,它可以支持 generator 与 async/await 一同使用:

JS Bin on jsbin.com

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function reducer(promise, action){
  let res = await promise;
  return action(res);
}
 
function tick(i){
  console.log(i);
  return new Promise(resolve => setTimeout(()=>resolve(++i), 1000));
}
 
function continuous(...functors){
  return async function(input){
    return await functors.reduce(reducer, input)
  }
}
 
function * timing(count = 5){
  for(let i = 0; i yield tick;
  }
}
 
continuous(...timing(10))(0);

在上面的例子里,我们定义了一个计时 tick 函数,我们通过 timing 来连续调用它,而 timing 是一个 generator,计时器显然是异步函数,然而我们可以:

 
 
1
continuous(...timing(10))(0);

而这里的 continuous 其实就是前面的 reduce 的封装。

使用 Proxy 实现 PHP 中的常用“魔术方法”

PHP 中有 __get 、 __set 和 __call 三个强大的魔术方法,可以实现对不存在的属性的读写和方法调用。在新的 ES 标准中添加了 Proxy 类,它可以构造 Proxy 对象,用来“重载”对象的属性和方法读写,从而实现类似于 PHP 的魔术方法:

JS Bin on jsbin.com

 
 
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function Magical(Class){
  return function(...args){
    return new Proxy(new Class(...args), {
      get: function(target, key, receiver) {
        if(typeof target[key] !== ‘undefined‘){
          return target[key];
        }
 
        if (typeof target.__get === ‘function‘){
          return target.__get(key);
        } else if (typeof target.__call === ‘function‘) {
          return function(...args){
            target.__call.apply(this, [key, ...args]);
          };
        }
      },
      set: function(target, key, value, receiver){
        if(typeof target[key] !== ‘undefined‘){
          target[key] = value;
          return;
        }
 
        if (typeof target.__set === ‘function‘){
          target.__set(key, value);
          return;
        }
      }
    });
  }
}
 
class Foo{
  __set(key, value){
    this[key] = value * 2;
  }
  __get(key){
    return this.b;
  }
  __call(key, ...args){
    console.log(`call method ${key} with ${args}`);
  }
  b(...args){
    console.log(`b exists: ${args}`);
  }
}
 
Foo = Magical(Foo);
 
var f = new Foo();
f.b(1,2,3);
f.a(4,5,6);
f.c = 3;
console.log(f.c);

上面的例子里,我们在对象构造的时候,分别“代理”对象实例的属性 get 和 set 方法,如果对象上已存在某个属性或方法,代理直接返回或操作该属性。否则,判断对象上是否有 __get、__set 或者 __call 方法,有的话,做相应的处理。

这里我们使用装饰器模式,定义了一个 Magical 装饰器函数,让它来处理希望使用 Magical 的类。

等到 ES Decorators 标准化了之后,我们就可以使用更加优雅的写法了:

 
 
1
2
3
4
5
6
@magical
class Foo {
    __call(key, ...args){
        ...
    }
}

 

本文源于 www.71key.com

JavaScript 最新特性实现的三大黑科技