微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

Promise 反模式

2016-05-13 20:55 前端大全

(点击上方公众号,可快速关注)


作者:淘宝前端团队(FED)- 云翮

链接:http://taobaofed.org/blog/2016/05/03/promise-anti-patterns/



Promises are about making asynchronous code retain most of the lost properties of synchronous code such as flat indentation and one exception channel. – Bluebird Wiki: Promise Anti Patterns


Promises 是为了让异步代码也能保持这些同步代码的属性:扁平缩进和单异常管道。


Deferred 反模式


这种反模式中,deferred 对象的创建是没有意义的,反而会增加代码的复杂度。


例如:


//Code copyright by Twisternha http://stackoverflow.com/a/19486699/995876 CC BY-SA 2.5

myApp.factory('Configurations', function (Restangular, MotorRestangular, $q) {

    var getConfigurations = function () {

        var deferred = $q.defer();

 

        MotorRestangular.all('Motors').getList().then(function (Motors) {

            //Group by Config

            var g = _.groupBy(Motors, 'configuration');

            //Map values

            var mapped = _.map(g, function (m) {

                return {

                    id: m[0].configuration,

                    configuration: m[0].configuration,

                    sizes: _.map(m, function (a) {

                        return a.sizeMm

                    })

                }

            });

            deferred.resolve(mapped);

        });

        return deferred.promise;

    };

 

    return {

        config: getConfigurations()

    }

 

});


这里的 deferred 对象并没有什么意义,而且可能在出错的情况下无法捕获。


正确的写法应该为:


myApp.factory('Configurations', function (Restangular, MotorRestangular, $q) {

    var getConfigurations = function () {

        //Just return the promise we already have!

        return MotorRestangular.all('Motors').getList().then(function (Motors) {

            //Group by Cofig

            var g = _.groupBy(Motors, 'configuration');

            //Return the mapped array as the value of this promise

            return _.map(g, function (m) {

                return {

                    id: m[0].configuration,

                    configuration: m[0].configuration,

                    sizes: _.map(m, function (a) {

                        return a.sizeMm

                    })

                }

            });

        });

    };

 

    return {

        config: getConfigurations()

    }

 

});


再举一个例子:


function applicationFunction(arg1) {

  var deferred = Promise.pending(); // 获取 Q.defer()

  libraryFunction(arg1, function(err, value) {

    if (err) {

      deferred.reject(err);

    } else {

      deferred.resolve(value);

    }

  });

  return deferred.promise;

}


这就像重复造轮子,因为回调 API 的封装应该使用 promise 库的 promisification(promise 化)方法实现:


var applicationFunction = Promise.promisify(libraryFunction);


通用 promise 化可能更快,因为可以借助 Promise 的内部操作,还能处理一些极端情况:例如 libraryFunction 同步抛异常或者用到了多个成功值。


什么时候使用 deferred?


必须用的时候。


当要封装的回调 API 和规范不一致时,例如 setTimeout:


// 通过 setTimeout 返回 promise

function delay(ms) {

  var deferred = Promise.pending();

  setTimeout(function() {

    deferred.resolve();

  }, ms);

  return deferred.promise;

}


.then(success, fail) 反模式


这样使用 .then 就像下面这段代码:


var t0;

try {

  t0 = doThat();

} catch(e) {

}

 

// 这样报错发生时会 catch 不到

var staff = JSON.parse(t0);


正常的同步写法是:


所以正确的 .then 用法应该是:


doThat()

.then(function(v) {

  return JSON.parse(v);

})

.catch(function(e) {

});


嵌套 Promise


例如:


loadSomething().then(function(something) {

  loadAnothering().then(function(another) {

    DoSomethingOnThem(something, another);

  });

});


如果只是想对两个 promise 的结果做处理,可以使用 Promise.all 方法:


Promise.all([loadSomething, loadAnothering]).then(function(something, another) {

  DoSomethingOnThem(something, another);

});


断链


例如:


function anAsyncCall() {

  var promise = doSomethingAsync();

  promise.then(function() {

    somethingComplicated();

  });

  return promise;

}


这里的问题在于加入 somethingComplicated() 出错的话不会被捕获。promise 应该链式调用。也就是说所有的 then 方法都应该返回一个新的 promise。所以上面代码的正确写法为:


function anAsyncCall() {

  var promise = doSomethingAsync();

  return promise.then(function() {

    somethingComplicated();

  });

}


集合


例如需要对一个集合中的每个元素执行异步操作:


function workMyCollection(arr) {

  var resultArr = [];

  function _recursive(idx) {

    if (idx >= resultArr.length) return resultArr;

    return doSomethingAsync(arr[idx]).then(function(res) {

      resultArr.push(res);

      return _recursive(idx + 1);

    });

  }

  return _recursive(0);

}


这里的问题在于需要遍历数组,其实可以用 promise.all 解决:


function workMyCollection(arr) {

  return q.all(

    arr.map(function(item) {

      return doSomethingAsync(item);

    })

  );    

}


总结


最容易犯的错误,没有使用 catch 去捕获 then 里抛出的报错:


// snippet1

somePromise().then(function () {

  throw new Error('oh noes');

}).catch(function (err) {

  // I caught your error! :)

});

 

// snippet2

somePromise().then(function resolve() {

  throw new Error('oh noes');

}, function reject(err) {

  // I didn't catch your error! :(

});


这里的问题在于 snippet2 在 function resolve 中出错时无法捕获。而 catch 则可以。


下面的两个示例返回结果是不一样的:


// example1

Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {

  console.log(result);  // foo

});

// example2

Promise.resolve('foo').then(function () {

  return Promise.resolve('bar')

}).then(function (result) {

  console.log(result); // bar

});


example2 改变了返回值,因而 result 发生了变化。


更多关键词


async flow control, event loop, callback, promises, generators, async/wait, coroutines


Promises 所做的承诺是保证异步代码顺序执行,并能够链式管理异常和错误。相比使用 event loop 和回调(callback)来控制异步代码的顺序执行,Promises 能够让代码更加清晰易懂。generator 更是从语言级别上提供了更好的支持。


V8 优化


V8 有两个编译器:通用编译器和优化编译器。也就是V8 中的 JavaScript 代码总是被编译成机器码后才执行的。


例如 a + b 编译成汇编代码为:


mov eax, a

mov ebx, b

call RuntimeAdd


但如果 a 和 b 都是整数的话,则会被编译成:


mov eax, a

mov ebx, b

add eax, ebx


这样编译后的代码性能会快很多,因为跳过了 JavaScript 对不同类型数据相加的处理。


通用编译器会得到前一种汇编码,优化编译器会得到后一种汇编码。两种汇编代码性能很容易产生 100 倍的差异。但是存在一些模式,这些模式下的代码优化编译器不会去处理(称为bail out)。Promises 属于一种被 bail out 的模式。


他山之石


Python greenlets(基于gevent)


go coroutine


参考资料


  • bluebird wiki

  • tao of code

  • https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

  • https://www.promisejs.org/patterns/

  • http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript

  • Node.js async exception handling

  • promise nuggets

  • Optimization Killers

  • v8 bailout reasons

  • Python greenlets


【今日微信公号推荐↓】

更多推荐请看值得关注的技术和设计公众号


其中推荐了包括技术设计极客 和 IT相亲相关的热门公众号。技术涵盖:Python、Web前端、Java、安卓、iOS、PHP、C/C++、.NET、Linux、数据库、运维、大数据、算法、IT职场等。点击《值得关注的技术和设计公众号》,发现精彩!


 
前端大全 更多文章 详解Javascript中的Object对象 结合个人经历总结的前端入门方法 前端不为人知的一面–前端冷知识集锦 一份优秀的前端开发工程师简历是怎么样的? 浅谈Web缓存
猜您喜欢 Android获取图片拍照时间 好的创意广告比文案更有力量! 成为数据分析师需要具备哪些技能? Facebook如何采集其Android应用性能数据 在 58 做测试的一天