Promise in ES6

在 JavaScript 世界中, 所有代码都是单线程执行的

异步执行可以可以用回调函数实现

function callback() {
  console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000);
console.log('after setTimeout()');
`</pre>

先看一个简单的 Promise 例子: 生成0-2之间的随机数, 如果小于1, 则等待一段时间后返回成功, 否则返回失败

<pre>`function test(resolve, reject){
  var timeOut = Math.random() * 2;
  log('set timeout to: ' + timeOut + ' seconds.');
  setTimeout(function(){
    if(timeOut &lt; 1) {
      log('call resolve()...');
      resolve('200 ok');
    } else {
      log('call reject()...');
      reject('timeout in ' + timeOut + ' seconds');
    }
  }, timeOut * 1000)
}
`</pre>

这个`test()`函数有两个参数, 这两个参数都是函数, 如果执行成功, 我们将调用`resolve('200 ok)'`, 如果执行失败, 我们将调用`reject('timeout in ' + timeOut + ' seconds.')`. 可以看出, `test()`函数只关心自己的逻辑, 并不关心具体的`resolve` 和`reject` 将如何处理.

有了执行函数, 我们就可以用一个 Promise 对象来执行他, 并在将来某个时刻获得成功或失败的结果

<pre>`var p1 = new Promsie(test);
var p2 = p1.then(function(result){
  console.log('成功: ' + result);
});
var p3 = p2.catch(function(err){
  console.log('失败: ' + err);
});
`</pre>

变量`p1`是一个 Promise 对象, 他负责执行执行`test`函数, 由于`test`函数在内部是异步执行的, 当`test`函数执行到`resolve('ok 200')`时, 告诉 Promsie 对象执行

<pre>`.then(function(result){ // result 是通过 test 传递给 resolve 的参数
  console.log('成功: ' + result);
});
`</pre>

当`test`执行到`reject`的时候, 告诉 Promise 对象执行

<pre>`.catch(function(err){ // err 是 test 传递给 reject 的参数
  console.log('失败: '+ err);
});
`</pre>

Promise 对象可以串联起来(因为都返回一个 Promise 对象)

<pre>`new Promise(test).then(function(result){console.log(result)}).catch(function(err){console.log(err)});
`</pre>

可见, Promise 最大的好处, 是在异步执行的流程中, 把执行代码和处理结果代码清晰地分离了.

Promise 还可以做更多的事情, 比如有若干个异步任务, 需要先做任务1, 如果成功后再做任务2, 任何任务失败则不再继续并执行错误处理函数

要串行执行这样的异步任务, 只需要链式调用

<pre>`job1.then(job2).then(job3).catch(handleError);
`</pre>

其中 job1, job2, job3 都是(或返回) Promise 对象

<pre>`function job2(input){
  return new Promise(function(resolve, reject){
      ....
    })
}
`</pre>

### 并行执行异步任务

试想一个页面聊天任务, 我们需要从两个不同的 URL 分别获取用户的个人信息和好友列表, 这两个任务是可以并行执行的, 用`Promise.all()`实现如下:

<pre>`var p1 = new Promise(function(resolve, reject){
  setTimeout(resolve, 500, 'P1');
});

var p2 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 600, 'P2');
});

// 同时执行p1 和 p2, 并在他们都执行完毕后执行 then
Promise.all([p1,p2]).then(function(results){
  console.log(results); // 获得一个 Array: ['P1', 'P2']
});
`</pre>

有时多个异步任务是为了容错, 比如同时向两个 URL 读取, 只需要获得先返回的结果, 这种情况下用`Promise.race()`实现:

<pre>`var p1 = new Promise(function(resolve, reject){
  setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function(resolve, reject){
  setTimeout(resolve, 600, 'P2');
});

Promise.race([p1,p2]).then(function(result){
  console.log(result); // 'P1'
});

彻底理解 Thunk 函数

生成器函数

生成器(Generator)函数写成:

function* func () {}
`</pre>

其本质也是一个函数, 所以他具备普通函数所具有的所有特性, 除此以外, 他还具有以下特性
  1. 执行生成器函数后返回一个迭代器(Iterator)
  2. 生成器函数内部可以使用 yield 或 yield*, 函数执行到 yield 的时候会暂停执行, 并返回 yield 后面表达式的值, 通过迭代器的 next() 方法返回一个对象, 该对象由 value 和 done 属性组成, 其中, value 属性值为 yield 后面表达式的值, done 属性表示生成器的执行状态
  3. 迭代器 next() 方法的参数是yield 表达式的值
  4. 当 next 指向最后一个 yield 的时候, 再次执行 next() 方法, 返回的对象中 done: true, 而 value 的值为 return 后面的表达式(默认为 undefined)

    例一

    `function test() {
      return 'b';
    }
    function* func() {
      var a = yield 'a';
    
      console.log('gen', a); // 'gen: undefined' - (如果 next 方法不带参数, yield 表达式返回 undefined)
      var b = yield test();
      console.log('gen': b); // 'gen: undefined'
    }
    
    var func1 = func();
    var a = func1.next();
    console.log('next: ' a); // 'next: {value: 'a', done: false}'
    var b = func1.next();
    console.log('next: ' b); // 'next: {value: 'b', done: false}'
    var c = func1.next();
    console.log('next: ' c); // 'next: {value: undefined, done: true}'
    `

    关于 yield*

    yield 暂停执行并返回后面表达式的值, 而 yield* 则将函数委托到另一个迭代器或可迭代对象

    arguments

    `> function* genFunc(){
    ... yield arguments;
    ... yield* arguments;
    ... }
    undefined
    > var gen = genFunc(1,2);
    undefined
    > gen.next()
    { value: { '0': 1, '1': 2 }, done: false }
    > gen.next()
    { value: 1, done: false }
    > gen.next()
    { value: 2, done: false }
    > gen.next()
    { value: undefined, done: true }
    `

    Generator

    `> function * gen1(){
    ... yield 2;
    ... yield 3;
    ... }
    undefined
    > function * gen2(){
    ... yield 1;
    ... yield* gen1();
    ... yield 4;
    ... }
    undefined
    > var g2 = gen2();
    undefined
    > g2.next()
    { value: 1, done: false }
    > g2.next()
    { value: 2, done: false }
    > g2.next()
    { value: 3, done: false }
    > g2.next()
    { value: 4, done: false }
    > g2.next()
    { value: undefined, done: true }
    `

    Thunk 函数

    在 co 的应用中, 为了能像同步代码那样书写异步代码, 比较多的使用方式是使用 thunk 函数(但不是唯一方式, 还可以是 Promise), 比如读取文件内容的异步函数 fs.readFile() 方法, 转化为 thunk 函数的方式如下

    `function readFile(path, encoding){
      return function(cb){
        fs.readFile(path, encoding, cb);
      }
    }
    `

    Thunk函数具备以下两个要素
    1. 有且只有一个参数是 callback 的函数
    2. callback 的第一个参数是 error

    使用 thunk 函数, 同时结合 co 我们就可以像书写同步代码一样书写异步代码

    `var co = require('co');
    var fs = require('fs');
    var Promise = require('es6-promise').Promise;
    
    function readFile(path, encoding){
      return function(cb) { // thunk function
        fs.readFile(path, encoding, cd);
      };
    }
    
    co(function* () { // 外部不可见, 但在 co 内部其实已经转化成promise.then().catch()...链式调用的行驶
      var a = yield readFile('a.txt', {encoding: 'utf-8'});
      console.log(a); // a
      var b = yield readFile('b.txt', {encoding: 'utf-8'});
      console.log(b); // b
      return yield Promise.resolve(a+b);
    }).then(function(val){
      console.log(val) // ab
    }).catch(function(error){
      console.log(error);
    })
    `

    有一个框架 thunkify 可以帮我们轻松实现以上步骤

    `var co = require('co');
    var thunkify = require('thunkify');
    var fs = requrie('fs');
    var Promise = require('es6-promise').Promise;
    
    var readFile = thunkify(fs.readFile);
    
    co(function* () { // 外部不可见, 但在 co 内部已经转化为 promise 的链式调用
      var a = yield readFile('a.txt', {encoding: 'utf-8'});
      console.log(a); // a
      var b = yield readFile('b.txt', {encoding: 'utf-8'});
      console.log(b); // b
      return yield Promise.resolve(a+b);
    }).then(function(val){
      console.log(val); // ab
    }).catch(function(err){
      console.log(err);
    })
    `

    对于 thunkify 框架的理解注释如下:

    `/**

    • Module dependencies
      */
      var assert = require(‘assert’);

    /**

    • Expose thunkify().
      */

    module.exports = thunkify;

    function thunkify(fn) {
    assert(‘function’ == typeof fn, ‘function required’);

    // 返回一个包含 thunk 函数的函数, 返回的 thunk 函数用于执行yield, 而外层的这个函数用于给 thunk 函数传递其他参数
    return function() {

    var args = new Array(arguments.length);
    // 缓存上下文环境, 给 fn 提供执行环境
    var ctx = this;
    
    // 将参数类数组对象转为数组(实现方式略臃肿, 可以直接用Array.from)
    for (var i = 0; i &lt; args.length; ++i){
      args[i] = argumetns[i];
    }
    
    // 真正的 thunk 函数(有且只有一个参数, 并且参数是第一个参数为 err 的 callback)
    return function(done) { // 返回 thunk 函数
      var called;
    
      // 将回调函数再包裹一层, 避免重复调用, 同时, 将包裹了的真正的回调函数 push 进参数数组(即将回调函数 done 作为最后一个参数传递给 fn)
      args.push(function(){  // 将回调函数 done 加到参数列表尾部, 形成 fn 需要的 [arg1, ..., done]
        if (called) return;
        called = true;
        done.apply(null, arguments); // 执行回调函数
      });
    
      try {
        // 在 ctx 环境执行 fn
        // 并将执行 thunkify 之后返回的函数的参数(含 done 回调) 传入, 类似于执行:
        // fs.readFile(path, {encoding: 'utf-8'}, done)
        fn.apply(ctx,args); // 类似 fn(arg1, .., done)
      } catch(err) {
        done(err);
      }
    }
    

    }
    }

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×