UDN-企业互联网技术人气社区

板块导航

浏览  : 1541
回复  : 0

[干货] 重审promise的用法

[复制链接]
芭芭拉的头像 楼主
发表于 2017-1-8 15:29:28 | 显示全部楼层 |阅读模式
  使用Promise已经很长时间了,体验着promise的强大的同时,时不时也会有不太自然的感觉, 但是也说不清哪里有问题。

  仔细想想,也许就是因为其强大,看什么都像是promise了,所以没有停下来思考一下到底某些场景使用promise是否可合适,以后也给自己提个醒,别轻易对某些东西太exciting了。

  promise的原始本质是把一个可能的将来值封装成一个无需关心时间的抽象值,把异步数据的使用方式和同步数据的使用方式统一起来,使得我们更容易的构建或者理解程序的逻辑。

  所以promise是一个值,虽然要获取它封装的原始值需要调用他的接口方法,但是它仍然是一个值,那么适合它使用的场景应该是值通常的使用场景。

  所以我们关心的部分应该是,值的构造、传递和保存。对于promise对象本身而言,作为一个抽象类型一定会遵循一定的标准,所以们还需要关心值的类型确认。

  从构造promise说起

  值的传递和保存应该很好理解,而promise的构造其实会是使用Promise的大头。

  常见的构造方式可以有下面几种,
  1. //直接调用其他人提供的接口生成一个promise对象
  2. var promise = promiseMaker(...);

  3. //由promise对象本身的方法衍生出新的promise对象
  4. var newPromise = promise.then(function(){...});

  5. //通过工具类方法把原有的promise对象加工生成新的promise对象
  6. var timeoutPromise = Promise.timeout(2000, promise);

  7. var promise = Promise.resolve(thenable);

  8. //使用原生构造方法构建promise对象
  9. var promise = new Promise(function(resolve, reject){...});
复制代码

  可以看出原生的构造方法使用起来最麻烦,需要知道的细节最多,也会使得代码更加复杂,应该尽量避免使用。

  第二,由于promise的异步性,尽量注意简洁,不要使用多余的promise来链接生成新promise,使用的多了性能还是会有一点损失的。

  第三,对于第三方返回的所谓的promise,我们不能轻易相信其合法性,保险起见可以使用 Promise.resolve 包一下。

  最后, 把promise分成 defer 和 defer.promise 是anti-pattern,不要使用该方式生成promise,或者利用原生构造方法使用该方式。

  promise是个值

  很容易就会把promise看成是一个异步运行的逻辑,有开始有结束,一次性的过程。而且在使用原生构造方法时,脑袋里面也是这样的思维模式。

  因此,很容易把好多一次性的过程场景都看成是promise了(我们已经干过好多这样的事),这里就是对promise too exciting了。

  promise抽象的是一个将来的值,用它来抽象过程其实是不合适的,我们有其他的模型作这样的事情,并且其他的模型会提供更丰富的处理方式,promise仅仅只有 then 方法,功能上太简单了。

  事件机制,观察者模式...还是使用合适的工具做合适的事情吧。

  回想一下,为了能结束某一过程,我们多少次把构造函数里的 resolve 和 reject 方法拆出去了,觉得很别扭,但就是不能跳出promise的框框看看还有什么更合适的处理方式。

  流程控制不是promise的责任
  1. p
  2.   .then(step1)
  3.   .then(step2)
  4.   .then(step3)
  5.   ...
复制代码

  上面的写法很熟悉吧。他比callback的写法也漂亮多了,但是还是回到promise的抽象上,这样的写法赋予promise的职责有点过重了,他不在单纯是封装值,还承担业务流程控制的职责。

  什么样的使用方式是符合其抽象的呢?
  1. var result = p
  2.     .then(convert1)
  3.     .then(convert2)
  4.     .then(convert3);
复制代码

  还是应该把promise视为值较为恰当, then 这里起着值转换的作用。

  讲究一点的话,上面 then 中接受的方法应该都是纯方法无副作用的。

  这意味着,拿promise链来一步一步赋值一个公共的变量,比如赋值一个全局对象,这样的做法也是违背promise原本抽象的精神的。

  所以对于异步流控制(上述赋值公共对象的操作也属于该范畴)应该采取其他机制来进行。

  这里新标准里的generator或者还没广泛支持的await/async,应该是更适合干流程控制这类事的,代码写起来也会和原来同步执行逻辑的代码差不多,也更好理解和reason。
  1. var makeResult = run(function gen*(arg){
  2.       var result = {};
  3.       var argB = yield promiseA(arg);
  4.       result.b = yield promiseB(argB);
  5.       try{
  6.          result.c = yield promiseC():
  7.       }catch(e){
  8.          console.warn(e);
  9.          result.c = 'defaultC';
  10.       }

  11.        result.d = 'd';
  12.        return result;
  13. });

  14. makeResult(arg).then(console.log.bind(console));
  15. //result:
  16. //{
  17. //    b: ..,
  18. //   c: ...,
  19. //    d: ...
  20. //}
复制代码

  上面的 run 方法的简单实现如下,
  1. function run(genMaker){
  2.   var generator = genMaker.apply(this, arguments);
  3.   function handle(result){
  4.     if(result.done){
  5.       return Promise.resolve(result.value);
  6.     }
  7.     return Promise.resolve(result.value)
  8.             .then(function(res){
  9.               return handle(generator.next(res));
  10.             })
  11.             .catch(function(err){
  12.               return handle(generator.throw(err));
  13.             });
  14.   }

  15.   try{
  16.     return handle(generator.next());
  17.   }catch(ex){
  18.     return Promise.reject(ex);
  19.   }
  20. }
复制代码

  回到我们自己的项目里,server端的异步操作逻辑会更复杂,需要流控制的场景会更多一些,好在当前的node版本已经支持使用generator了,所以鼓励server端多尝试该方式编码。

  前端的异步操作逻辑相对简单一些,目前还没有看到使用需求,以后可以添加编译过程来支持generator的使用。

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部