chshouyu

chshouyu 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

chshouyu 赞了回答 · 2015-06-15

解决请教一下nodejs package.json的依赖关系定义

devDependencies 是开发时依赖,比如你模块用了mocha测试框架,那么你的模块的开发就依赖 mocha,如果别人想为你的模块贡献代码,他就需要安装mocha。但是只是使用你的模块的人,就不需要mocha。

peerDependencies是为插件准备的。比如grunt的插件,里面没有“require("grunt")”,所以用dependencies会有问题。所以需要单独列出。

关注 2 回答 1

chshouyu 赞了回答 · 2015-06-04

解决js 正则: `^(ab|a)$` 与 `^[ab|a]$` 匹配的值不同

关注 12 回答 6

chshouyu 发布了文章 · 2014-07-23

Mac中git的安装问题

mac中的git默认是随着xcode一起安装的,所以一般不必手动安装。但是随着时间的推移,git版本不断升级,自带的git就显得有点老旧了,因此我们需要自己来安装最新的版本。

有两种方式可以安装git

安装完之后,执行git --version,如果你发现依旧是老版本,可以使用下面的方式解决。

以上两种方式,最终都会把执行路径放到/usr/local/bin下面,xcode安装的git会放在/usr/bin下面,而且如果你查看一下PATH

echo $PATH

会发现,/usr/bin是处在/usr/local/bin前面的,所以就导致了旧版本依旧覆盖了新版本。知道了原因之后,解决方式就很简单了,我们只需要把/usr/local/bin放到前面即可。有两种方式

第一种:
编辑/etc/paths(需要管理员权限),调整/usr/local/bin在最前面

第二种:
编辑~/.bashrc或者~/.bash_profile

PATH=/usr/local/bin:$PATH
export PATH

然后执行source ~/.bashrc或者source ~/.bash_profile使改变生效。

此时再次执行git --version应该就会看到新版本了。

查看原文

赞 2 收藏 2 评论 1

chshouyu 收藏了文章 · 2014-07-15

[译] 深入理解 Promise 五部曲:2. 控制权转换问题

原文地址:http://blog.getify.com/promis...

厦门旅行归来,继续理解Promise

在上一篇深入理解Promise五部曲:1.异步问题中,我们揭示了JS的异步事件轮询并发模型并且解释了多任务是如何相互穿插使得它们看起来像是同时运行的。然后我们讨论了为什么我们努力地在我们的代码里表达这些东西以及为什么我们的大脑不善于理解它们。

我们现在要找出一个更好的方式来表达异步流程,然后看看Promises是怎么解决这个问题的。

回调嵌套

JS从一开始就使用事件轮询的并发模型。我们一直以来都在写异步的程序。直到最近,我们仍然在用简单的回调函数来处理异步的问题。

makeAjaxRequest(url,function(respnose){
    alert("Response:" + response) ;
}) ;

当我们只有一个异步任务的时候使用回调函数看起来还不会有什么问题。但是,实际是我们完成一个任务通常需要多个异步操作。例如:

btn.addEventListener("click",function(evt){
    makeAjaxRequest(url,function(response){
        makeAjaxRequest(anotherURL + "?resp=" + response,function(response2){
            alert("Response2:" + response) ;
        })
    }) ;
},false) ;

把一系列异步操作链接在一起最自然的方式就是使用回调嵌套,步骤2嵌套在步骤1中然后步骤3嵌套在步骤2中,等等。

回调地狱

你使用越多的回调,就会有越多的嵌套,不断缩进意大利面条似的代码。很显然,这种代码难以编写,难以理解而且难以维护。如果我们花点时间来理清这些代码往往会让我们事半功倍。这类嵌套/缩进经常被叫做"回调地狱"。有时也被叫做"回调金字塔",专指由于代码不断缩进所形成的金字塔形状,缩进越多金字塔形状越明显。

但是我还是觉得"回调地狱"真的跟嵌套和缩进扯不上太大的关系。如果之前有人跟你说回调地狱就是指嵌套和缩进的话,不要相信他,因为他们并不理解回调真正的问题在哪儿。

可靠性缺失

回调(无论是否有嵌套)的真正问题是远比编辑器中的空白符严重。让我们来分析下下面这个简单的回调发生了什么

//1.everything in my program before now

someAsyncThing(function(){
    //2.everything in my program for later
}) ;

你看清这段代码说了什么吗?你从根本上把你的程序分成了两个部分:

  1. 直到现在为止发生的事情

  2. 以后会发生的事情

换句话说,你把第二部分代码包装在一个回调函数中然后延迟到后面执行。

但是这并不是问题,真正问题是在1和2之间发生了什么。请问在这段时间内是谁在控制这些。
someAsyncThing(..)控制着这些。是你自己拥有并管理someAsyncThing()吗?许多时候不是。更重要的是,你有多信任someAsyncThing(..)?你会问,信任什么?不管你意识到没有,你潜在的相信someAsyncThing(..)会做到下面这些:

  1. 不会太早调用我的回调函数

  2. 不会太迟调用我的回调函数(1,2就是说会在适当的时候调用回调函数)

  3. 不会调用我的回调太少次(不会少于实际应该调用的次数,比如不会漏掉函数调用)

  4. 不会调用我的回调太多次(不会多于实际应该调用的次数,比如重复调用)

  5. 会给我的回调提供必要的参数

  6. 在我的回调失败的时候会提醒我

咳!你也太信任它了!

实际上,这里真正的问题是由于回调引起的控制转移。在你的程序的前半部分,你控制着程序的进程。现在你转移了控制权,someAsyncThing(..)控制了你剩余程序什么时候返回以及是否返回。控制转移表明了你的代码和其他人的代码之间的过度信任关系。

恐吓战术

someAsyncThing(..)是第三方库的一个方法并且你无法控制不能检查的时候会发生什么?只能祝你好运了!

比如你有一个电子商务网站,用户就要完成付款的步骤了,但是在扣费之前有最后一个步骤,它需要通知一个第三方跟踪库。你调用他们API,并且提供一个回调函数。大部分情况下,这不会有什么问题。但是,在这次业务中,有一些你和他们都没有意识到的奇怪的Bug,结果就是第三方库在超时之前五秒的时间内每隔一秒就会调用一次回调函数。猜猜发生了什么?在这个回调里调用了chargeTheCreditCard()

Oops,消费者被扣了五次钱。为什么?因为你相信第三方库只会调用你的回调一次。

所以你不得不被丢鸡蛋并且给消费者道歉归还多扣的四次钱。然后你立刻采取措施确保这种情况不会再发生。你会怎么做呢?

你可能会创建一些状态值来跟踪你的回调,当它被调用一次之后会被标记,然后就可以忽略任何意外的重复调用。无论第三方如何道歉并且承诺他们的bug已经修复了,你再也不会相信他们了,不是吗?

这看起来像一个愚蠢的场景,但是这可能比你想得还普遍。我们的程序变得越复杂,我们就会集成越多的第三方/外部代码,这种愚蠢的场景就越容易发生。

布基胶带

你给你的回调加入了状态跟踪机制,然后睡了一个好觉。但是实际上你只是处理了信任列表许多项目中的一项。当另一个bug造成另一个可靠性丢失的情况时会发生什么?更多的改造,更多丑陋的代码。

更多布基胶带。你必须不断修复回调中的漏洞。无论你是多优秀的开发者,无论你的布基胶带多漂亮,事实就是:在你信任墙上的回调充满了漏洞。

Promise解决方案

一些人喜欢使用布基绷带并且给信任墙上的洞打补丁。但是在某些时候,你也许会问自己,是否有其他模式来表达异步流程控制,不需要忍受所有这些可靠性丢失?

是的!Promises就是一个方法。

在我解释它们是怎么工作之前,让我来解释一些它们背后的概念问题。

快餐业务

你走进你最喜爱的快餐店,走到前台要了一些美味的食物。收银员告诉你一共7.53美元然后你把钱给她。她会给回你什么东西呢?

如果你足够幸运,你要的食物已经准备好了。但是大多数情况下,你会拿到一个写着序列号的小票,是吧?所以你站到一边等待你的食物。

很快,你听到广播响起:“请317号取餐”。正好是你的号码。你走到前台用小票换来你的食物!谢天谢地,你不用忍受太长的等待。

刚才发生的是一个对于Promises很好的比喻。你走到前台开始一个业务,但是这个业务不能马上完成。所以,你得到一个在迟些时候完成业务(你的食物)的promise(小票)。一旦你的食物准备就绪,你会得到通知然后你第一时间用你的promise(小票)换来了你想要的东西:食物。

换句话说,带有序列号的小票就是对于一个未来结果的承诺。

完成事件

想想上面调用someAsyncThing(..)的例子。如果你可以调用它然后订阅一个事件,当这个调用完成的时候你会得到通知而不是传递一个回调给它,这样难道不会更好吗?

例如,想象这样的代码:

var listener = someAsyncThing(..) ;
listener.on("completion",function(data){
    //keep going now !
}) ;

实际上,如果我们还可以监听调用失败的事件那就更好了。

listener.on("failure",function(){
    //Oops,What's plan B?
}) ;

现在,对于我们调用的每个函数,我们能够在函数成功执行或者失败的时候得到通知。换句话说,每个函数调用会是流程控制图上的决策点。

Promise"事件"

Promises就像是一个函数在说“我这有一个事件监听器,当我完成或者失败的时候会被通知到。”我们看看它是怎么工作的:

function someAsyncThing(){
    var p = new Promise(function(resolve,reject){
        //at some later time,call 'resolve()' or 'reject()'
    }) ;
    return p ;
}
var p = someAsyncThing() ;
p.then(
    function(){
        //success happened    
    },
    function(){
        //failure happened
    }
) ;

你只需要监听then事件,然后通过知道哪个回调函数被调用就可以知道是成功还是失败。

逆转

通过promises,我们重新获得了程序的控制权而不是通过给第三方库传递回调来转移控制权。这是javascript中异步控制流程表达上一个很大的进步。

“等等”,你说。“我仍然要传递回调啊。有什么不一样?!”嗯。。。好眼力!

有些人声称Promises通过移除回调来解决“回调地狱”的问题。并不是这样!在一些情况下,你甚至需要比以前更多的回调。同时,根据你如何编写你的代码,你可能仍然需要把promises嵌套在别的promises中!

批判性地看,promises所做的只是改变了你传递回调的地方。

本质上,如果你把你的回调传递给拥有良好保证和可预测性的中立Promises机制,你实质上重新获得了对于后续程序能很稳定并且运行良好的可靠性。标准的promises机制有以下这些保证:

  1. 如果promise被resolve,它要不是success就是failure,不可能同时存在。

  2. 一旦promise被resolve,它就再也不会被resolve(不会出现重复调用)。

  3. 如果promise返回了成功的信息,那么你绑定在成功事件上的回调会得到这个消息。

  4. 如果发生了错误,promise会收到一个带有错误信息的错误通知。

  5. 无论promise最后的结果是什么(success或者failure),他就不会改变了,你总是可以获得这个消息只要你不销毁promise。

如果我们从someAsyncThing(..)得到的promise不是可用的标准的promise会发生什么?如果我们无法判断我们是否可相信它是真的promise会怎么样?

简单!只要你得到的是“类promise”的,也就是拥有then(..)方法可以注册success和failure事件,那么你就可用使用这个“类promise”然后把它包装在一个你信任的promise中。

var notSureWhatItIs = someAsyncThing();

var p = Promise.resolve( notSureWhatItIs );

// now we can trust `p`!!
p.then(
    function(){
        // success happened 
    },
    function(){
        // failure happened 
    }
);

promises的最重要的特点就是它把我们处理任何函数调用的成功或者失败的方式规范成了可预测的形式,特别是如果这个调用实际上的异步的。

在这个规范过程中,它使我们的程序在可控制的位置而不是把控制权交给一个不可相信的第三方。

总结

不要管你所听到的,“回调地狱”不是真的关于函数嵌套和它们在代码编辑器中产生的缩进。它是关于控制转移的,是指我们由于把控制权交给一个我们不能信任的第三方而产生的对我们的程序失去控制的现象。

Promises逆转了这个情况,它使得我们重新获得控制权。相比传递回调给第三方函数,函数返回一个promise对象,我们可以使用它来监听函数的成功或失败。在promise我们仍然使用回调,但是重要的是标准的promise机制使我们可以信任它们行为的正确性。我们不需要想办法来处理这些可靠性问题。

在第三部分:可靠性问题中,我会说道一个promises可靠性机制中很特别的部分:一个promise的状态必须是可靠并且不可变的。

深入理解Promise五部曲--1.异步问题
深入理解Promise五部曲--2.转换问题
深入理解Promise五部曲--3.可靠性问题
深入理解Promise五部曲--4.扩展性问题
深入理解Promise五部曲--5.乐高问题

最后,安利下我的个人博客,欢迎访问:http://bin-playground.top

查看原文

chshouyu 赞了文章 · 2014-07-15

[译] 深入理解 Promise 五部曲:2. 控制权转换问题

原文地址:http://blog.getify.com/promis...

厦门旅行归来,继续理解Promise

在上一篇深入理解Promise五部曲:1.异步问题中,我们揭示了JS的异步事件轮询并发模型并且解释了多任务是如何相互穿插使得它们看起来像是同时运行的。然后我们讨论了为什么我们努力地在我们的代码里表达这些东西以及为什么我们的大脑不善于理解它们。

我们现在要找出一个更好的方式来表达异步流程,然后看看Promises是怎么解决这个问题的。

回调嵌套

JS从一开始就使用事件轮询的并发模型。我们一直以来都在写异步的程序。直到最近,我们仍然在用简单的回调函数来处理异步的问题。

makeAjaxRequest(url,function(respnose){
    alert("Response:" + response) ;
}) ;

当我们只有一个异步任务的时候使用回调函数看起来还不会有什么问题。但是,实际是我们完成一个任务通常需要多个异步操作。例如:

btn.addEventListener("click",function(evt){
    makeAjaxRequest(url,function(response){
        makeAjaxRequest(anotherURL + "?resp=" + response,function(response2){
            alert("Response2:" + response) ;
        })
    }) ;
},false) ;

把一系列异步操作链接在一起最自然的方式就是使用回调嵌套,步骤2嵌套在步骤1中然后步骤3嵌套在步骤2中,等等。

回调地狱

你使用越多的回调,就会有越多的嵌套,不断缩进意大利面条似的代码。很显然,这种代码难以编写,难以理解而且难以维护。如果我们花点时间来理清这些代码往往会让我们事半功倍。这类嵌套/缩进经常被叫做"回调地狱"。有时也被叫做"回调金字塔",专指由于代码不断缩进所形成的金字塔形状,缩进越多金字塔形状越明显。

但是我还是觉得"回调地狱"真的跟嵌套和缩进扯不上太大的关系。如果之前有人跟你说回调地狱就是指嵌套和缩进的话,不要相信他,因为他们并不理解回调真正的问题在哪儿。

可靠性缺失

回调(无论是否有嵌套)的真正问题是远比编辑器中的空白符严重。让我们来分析下下面这个简单的回调发生了什么

//1.everything in my program before now

someAsyncThing(function(){
    //2.everything in my program for later
}) ;

你看清这段代码说了什么吗?你从根本上把你的程序分成了两个部分:

  1. 直到现在为止发生的事情

  2. 以后会发生的事情

换句话说,你把第二部分代码包装在一个回调函数中然后延迟到后面执行。

但是这并不是问题,真正问题是在1和2之间发生了什么。请问在这段时间内是谁在控制这些。
someAsyncThing(..)控制着这些。是你自己拥有并管理someAsyncThing()吗?许多时候不是。更重要的是,你有多信任someAsyncThing(..)?你会问,信任什么?不管你意识到没有,你潜在的相信someAsyncThing(..)会做到下面这些:

  1. 不会太早调用我的回调函数

  2. 不会太迟调用我的回调函数(1,2就是说会在适当的时候调用回调函数)

  3. 不会调用我的回调太少次(不会少于实际应该调用的次数,比如不会漏掉函数调用)

  4. 不会调用我的回调太多次(不会多于实际应该调用的次数,比如重复调用)

  5. 会给我的回调提供必要的参数

  6. 在我的回调失败的时候会提醒我

咳!你也太信任它了!

实际上,这里真正的问题是由于回调引起的控制转移。在你的程序的前半部分,你控制着程序的进程。现在你转移了控制权,someAsyncThing(..)控制了你剩余程序什么时候返回以及是否返回。控制转移表明了你的代码和其他人的代码之间的过度信任关系。

恐吓战术

someAsyncThing(..)是第三方库的一个方法并且你无法控制不能检查的时候会发生什么?只能祝你好运了!

比如你有一个电子商务网站,用户就要完成付款的步骤了,但是在扣费之前有最后一个步骤,它需要通知一个第三方跟踪库。你调用他们API,并且提供一个回调函数。大部分情况下,这不会有什么问题。但是,在这次业务中,有一些你和他们都没有意识到的奇怪的Bug,结果就是第三方库在超时之前五秒的时间内每隔一秒就会调用一次回调函数。猜猜发生了什么?在这个回调里调用了chargeTheCreditCard()

Oops,消费者被扣了五次钱。为什么?因为你相信第三方库只会调用你的回调一次。

所以你不得不被丢鸡蛋并且给消费者道歉归还多扣的四次钱。然后你立刻采取措施确保这种情况不会再发生。你会怎么做呢?

你可能会创建一些状态值来跟踪你的回调,当它被调用一次之后会被标记,然后就可以忽略任何意外的重复调用。无论第三方如何道歉并且承诺他们的bug已经修复了,你再也不会相信他们了,不是吗?

这看起来像一个愚蠢的场景,但是这可能比你想得还普遍。我们的程序变得越复杂,我们就会集成越多的第三方/外部代码,这种愚蠢的场景就越容易发生。

布基胶带

你给你的回调加入了状态跟踪机制,然后睡了一个好觉。但是实际上你只是处理了信任列表许多项目中的一项。当另一个bug造成另一个可靠性丢失的情况时会发生什么?更多的改造,更多丑陋的代码。

更多布基胶带。你必须不断修复回调中的漏洞。无论你是多优秀的开发者,无论你的布基胶带多漂亮,事实就是:在你信任墙上的回调充满了漏洞。

Promise解决方案

一些人喜欢使用布基绷带并且给信任墙上的洞打补丁。但是在某些时候,你也许会问自己,是否有其他模式来表达异步流程控制,不需要忍受所有这些可靠性丢失?

是的!Promises就是一个方法。

在我解释它们是怎么工作之前,让我来解释一些它们背后的概念问题。

快餐业务

你走进你最喜爱的快餐店,走到前台要了一些美味的食物。收银员告诉你一共7.53美元然后你把钱给她。她会给回你什么东西呢?

如果你足够幸运,你要的食物已经准备好了。但是大多数情况下,你会拿到一个写着序列号的小票,是吧?所以你站到一边等待你的食物。

很快,你听到广播响起:“请317号取餐”。正好是你的号码。你走到前台用小票换来你的食物!谢天谢地,你不用忍受太长的等待。

刚才发生的是一个对于Promises很好的比喻。你走到前台开始一个业务,但是这个业务不能马上完成。所以,你得到一个在迟些时候完成业务(你的食物)的promise(小票)。一旦你的食物准备就绪,你会得到通知然后你第一时间用你的promise(小票)换来了你想要的东西:食物。

换句话说,带有序列号的小票就是对于一个未来结果的承诺。

完成事件

想想上面调用someAsyncThing(..)的例子。如果你可以调用它然后订阅一个事件,当这个调用完成的时候你会得到通知而不是传递一个回调给它,这样难道不会更好吗?

例如,想象这样的代码:

var listener = someAsyncThing(..) ;
listener.on("completion",function(data){
    //keep going now !
}) ;

实际上,如果我们还可以监听调用失败的事件那就更好了。

listener.on("failure",function(){
    //Oops,What's plan B?
}) ;

现在,对于我们调用的每个函数,我们能够在函数成功执行或者失败的时候得到通知。换句话说,每个函数调用会是流程控制图上的决策点。

Promise"事件"

Promises就像是一个函数在说“我这有一个事件监听器,当我完成或者失败的时候会被通知到。”我们看看它是怎么工作的:

function someAsyncThing(){
    var p = new Promise(function(resolve,reject){
        //at some later time,call 'resolve()' or 'reject()'
    }) ;
    return p ;
}
var p = someAsyncThing() ;
p.then(
    function(){
        //success happened    
    },
    function(){
        //failure happened
    }
) ;

你只需要监听then事件,然后通过知道哪个回调函数被调用就可以知道是成功还是失败。

逆转

通过promises,我们重新获得了程序的控制权而不是通过给第三方库传递回调来转移控制权。这是javascript中异步控制流程表达上一个很大的进步。

“等等”,你说。“我仍然要传递回调啊。有什么不一样?!”嗯。。。好眼力!

有些人声称Promises通过移除回调来解决“回调地狱”的问题。并不是这样!在一些情况下,你甚至需要比以前更多的回调。同时,根据你如何编写你的代码,你可能仍然需要把promises嵌套在别的promises中!

批判性地看,promises所做的只是改变了你传递回调的地方。

本质上,如果你把你的回调传递给拥有良好保证和可预测性的中立Promises机制,你实质上重新获得了对于后续程序能很稳定并且运行良好的可靠性。标准的promises机制有以下这些保证:

  1. 如果promise被resolve,它要不是success就是failure,不可能同时存在。

  2. 一旦promise被resolve,它就再也不会被resolve(不会出现重复调用)。

  3. 如果promise返回了成功的信息,那么你绑定在成功事件上的回调会得到这个消息。

  4. 如果发生了错误,promise会收到一个带有错误信息的错误通知。

  5. 无论promise最后的结果是什么(success或者failure),他就不会改变了,你总是可以获得这个消息只要你不销毁promise。

如果我们从someAsyncThing(..)得到的promise不是可用的标准的promise会发生什么?如果我们无法判断我们是否可相信它是真的promise会怎么样?

简单!只要你得到的是“类promise”的,也就是拥有then(..)方法可以注册success和failure事件,那么你就可用使用这个“类promise”然后把它包装在一个你信任的promise中。

var notSureWhatItIs = someAsyncThing();

var p = Promise.resolve( notSureWhatItIs );

// now we can trust `p`!!
p.then(
    function(){
        // success happened 
    },
    function(){
        // failure happened 
    }
);

promises的最重要的特点就是它把我们处理任何函数调用的成功或者失败的方式规范成了可预测的形式,特别是如果这个调用实际上的异步的。

在这个规范过程中,它使我们的程序在可控制的位置而不是把控制权交给一个不可相信的第三方。

总结

不要管你所听到的,“回调地狱”不是真的关于函数嵌套和它们在代码编辑器中产生的缩进。它是关于控制转移的,是指我们由于把控制权交给一个我们不能信任的第三方而产生的对我们的程序失去控制的现象。

Promises逆转了这个情况,它使得我们重新获得控制权。相比传递回调给第三方函数,函数返回一个promise对象,我们可以使用它来监听函数的成功或失败。在promise我们仍然使用回调,但是重要的是标准的promise机制使我们可以信任它们行为的正确性。我们不需要想办法来处理这些可靠性问题。

在第三部分:可靠性问题中,我会说道一个promises可靠性机制中很特别的部分:一个promise的状态必须是可靠并且不可变的。

深入理解Promise五部曲--1.异步问题
深入理解Promise五部曲--2.转换问题
深入理解Promise五部曲--3.可靠性问题
深入理解Promise五部曲--4.扩展性问题
深入理解Promise五部曲--5.乐高问题

最后,安利下我的个人博客,欢迎访问:http://bin-playground.top

查看原文

赞 8 收藏 50 评论 6

chshouyu 评论了文章 · 2014-07-14

JavaScript Promise启示录

chshouyu 关注了标签 · 2014-07-12

svn

svn(subversion)是近年来崛起的版本管理工具,是cvs的接班人。目前,绝大多数开源软件都使用svn作为代码版本管理软件。

关注 742

chshouyu 赞了文章 · 2014-07-10

JavaScript Promise启示录

本篇,简单实现一个promise,主要普及promise的用法。

一直以来,JavaScript处理异步都是以callback的方式,在前端开发领域callback机制几乎深入人心。在设计API的时候,不管是浏览器厂商还是SDK开发商亦或是各种类库的作者,基本上都已经遵循着callback的套路。

近几年随着JavaScript开发模式的逐渐成熟,CommonJS规范顺势而生,其中就包括提出了Promise规范,Promise完全改变了js异步编程的写法,让异步编程变得十分的易于理解。

在callback的模型里边,我们假设需要执行一个异步队列,代码看起来可能像这样:

loadImg('a.jpg', function() {
    loadImg('b.jpg', function() {
        loadImg('c.jpg', function() {
            console.log('all done!');
        });
    });
});

这也就是我们常说的回调金字塔,当异步的任务很多的时候,维护大量的callback将是一场灾难。当今Node.js大热,好像很多团队都要用它来做点东西以沾沾“洋气”,曾经跟一个运维的同学聊天,他们也是打算使用Node.js做一些事情,可是一想到js的层层回调就望而却步。

好,扯淡完毕,下面进入正题。

Promise可能大家都不陌生,因为Promise规范已经出来好一段时间了,同时Promise也已经纳入了ES6,而且高版本的chrome、firefox浏览器都已经原生实现了Promise,只不过和现如今流行的类Promise类库相比少些API。

所谓Promise,字面上可以理解为“承诺”,就是说A调用B,B返回一个“承诺”给A,然后A就可以在写计划的时候这么写:当B返回结果给我的时候,A执行方案S1,反之如果B因为什么原因没有给到A想要的结果,那么A执行应急方案S2,这样一来,所有的潜在风险都在A的可控范围之内了。

上面这句话,翻译成代码类似:

var resB = B();
var runA = function() {
    resB.then(execS1, execS2);
};
runA();

只看上面这行代码,好像看不出什么特别之处。但现实情况可能比这个复杂许多,A要完成一件事,可能要依赖不止B一个人的响应,可能需要同时向多个人询问,当收到所有的应答之后再执行下一步的方案。最终翻译成代码可能像这样:

var resB = B();
var resC = C();
...

var runA = function() {
    reqB
        .then(resC, execS2)
        .then(resD, execS3)
        .then(resE, execS4)
        ...
        .then(execS1);
};

runA();

在这里,当每一个被询问者做出不符合预期的应答时都用了不同的处理机制。事实上,Promise规范没有要求这样做,你甚至可以不做任何的处理(即不传入then的第二个参数)或者统一处理。

好了,下面我们来认识下Promise/A+规范

  • 一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)

  • 一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换

  • promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致

  • then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。

可以看到,Promise规范的内容并不算多,大家可以试着自己实现以下Promise。

以下是笔者自己在参考许多类Promise库之后简单实现的一个Promise,代码请移步promiseA

简单分析下思路:

构造函数Promise接受一个函数resolver,可以理解为传入一个异步任务,resolver接受两个参数,一个是成功时的回调,一个是失败时的回调,这两参数和通过then传入的参数是对等的。

其次是then的实现,由于Promise要求then必须返回一个promise,所以在then调用的时候会新生成一个promise,挂在当前promise的_next上,同一个promise多次调用都只会返回之前生成的_next

由于then方法接受的两个参数都是可选的,而且类型也没限制,可以是函数,也可以是一个具体的值,还可以是另一个promise。下面是then的具体实现:

Promise.prototype.then = function(resolve, reject) {
    var next = this._next || (this._next = Promise());
    var status = this.status;
    var x;

    if('pending' === status) {
        isFn(resolve) && this._resolves.push(resolve);
        isFn(reject) && this._rejects.push(reject);
        return next;
    }

    if('resolved' === status) {
        if(!isFn(resolve)) {
            next.resolve(resolve);
        } else {
            try {
                x = resolve(this.value);
                resolveX(next, x);
            } catch(e) {
                this.reject(e);
            }
        }
        return next;
    }

    if('rejected' === status) {
        if(!isFn(reject)) {
            next.reject(reject);
        } else {
            try {
                x = reject(this.reason);
                resolveX(next, x);
            } catch(e) {
                this.reject(e);
            }
        }
        return next;
    }
};

这里,then做了简化,其他promise类库的实现比这个要复杂得多,同时功能也更多,比如还有第三个参数——notify,表示promise当前的进度,这在设计文件上传等时很有用。对then的各种参数的处理是最复杂的部分,有兴趣的同学可以参看其他类Promise库的实现。

在then的基础上,应该还需要至少两个方法,分别是完成promise的状态从pending到resolved或rejected的转换,同时执行相应的回调队列,即resolve()reject()方法。

到此,一个简单的promise就设计完成了,下面简单实现下两个promise化的函数:

function sleep(ms) {
    return function(v) {
        var p = Promise();

        setTimeout(function() {
            p.resolve(v);
        });

        return p;
    };
};

function getImg(url) {
    var p = Promise();
    var img = new Image();

    img.onload = function() {
        p.resolve(this);
    };

    img.onerror = function(err) {
        p.reject(err);
    };

    img.url = url;

    return p;
};

由于Promise构造函数接受一个异步任务作为参数,所以getImg还可以这样调用:

function getImg(url) {
    return Promise(function(resolve, reject) {
        var img = new Image();

        img.onload = function() {
            resolve(this);
        };

        img.onerror = function(err) {
            reject(err);
        };

        img.url = url;
    });
};

接下来(见证奇迹的时刻),假设有一个BT的需求要这么实现:异步获取一个json配置,解析json数据拿到里边的图片,然后按顺序队列加载图片,没张图片加载时给个loading效果

function addImg(img) {
    $('#list').find('> li:last-child').html('').append(img);
};

function prepend() {
    $('<li>')
        .html('loading...')
        .appendTo($('#list'));
};

function run() {
    $('#done').hide();
    getData('map.json')
        .then(function(data) {
            $('h4').html(data.name);

            return data.list.reduce(function(promise, item) {
                return promise
                    .then(prepend)
                    .then(sleep(1000))
                    .then(function() {
                        return getImg(item.url);
                    })
                    .then(addImg);
            }, Promise.resolve());
        })
        .then(sleep(300))
        .then(function() {
            $('#done').show();
        });
};

$('#run').on('click', run);

这里的sleep只是为了看效果加的,可猛击查看demo!当然,Node.js的例子可查看这里

在这里,Promise.resolve(v)静态方法只是简单返回一个以v为肯定结果的promise,v可不传入,也可以是一个函数或者是一个包含then方法的对象或函数(即thenable)。

类似的静态方法还有Promise.cast(promise),生成一个以promise为肯定结果的promise;

Promise.reject(reason),生成一个以reason为否定结果的promise。

我们实际的使用场景可能很复杂,往往需要多个异步的任务穿插执行,并行或者串行同在。这时候,可以对Promise进行各种扩展,比如实现Promise.all(),接受promises队列并等待他们完成再继续,再比如Promise.any(),promises队列中有任何一个处于完成态时即触发下一步操作。

标准的Promise

可参考html5rocks的这篇文章JavaScript Promises,目前高级浏览器如chrome、firefox都已经内置了Promise对象,提供更多的操作接口,比如Promise.all(),支持传入一个promises数组,当所有promises都完成时执行then,还有就是更加友好强大的异常捕获,应对日常的异步编程,应该足够了。

第三方库的Promise

现今流行的各大js库,几乎都不同程度的实现了Promise,如dojo,jQuery、Zepto、when.js、Q等,只是暴露出来的大都是Deferred对象,以jQuery(Zepto类似)为例,实现上面的getImg()

function getImg(url) {
    var def = $.Deferred();
    var img = new Image();

    img.onload = function() {
        def.resolve(this);
    };

    img.onerror = function(err) {
        def.reject(err);
    };

    img.src = url;

    return def.promise();
};

当然,jQuery中,很多的操作都返回的是Deferred或promise,如animateajax

// animate
$('.box')
    .animate({'opacity': 0}, 1000)
    .promise()
    .then(function() {
        console.log('done');
    });

// ajax
$.ajax(options).then(success, fail);
$.ajax(options).done(success).fail(fail);

// ajax queue
$.when($.ajax(options1), $.ajax(options2))
    .then(function() {
        console.log('all done.');
    }, function() {
        console.error('There something wrong.');
    });

jQuery还实现了done()fail()方法,其实都是then方法的shortcut。

处理promises队列,jQuery实现的是$.when()方法,用法和Promise.all()类似。

其他类库,这里值得一提的是when.js,本身代码不多,完整实现Promise,同时支持browser和Node.js,而且提供更加丰富的API,是个不错的选择。这里限于篇幅,不再展开。

尾声

我们看到,不管Promise实现怎么复杂,但是它的用法却很简单,组织的代码很清晰,从此不用再受callback的折磨了。

最后,Promise是如此的优雅!但Promise也只是解决了回调的深层嵌套的问题,真正简化JavaScript异步编程的还是Generator,在Node.js端,建议考虑Generator。

下一篇,研究下Generator。

github原文:https://github.com/chemdemo/chemdemo.github.io/issues/6

参考文献

查看原文

赞 2 收藏 45 评论 12

chshouyu 收藏了文章 · 2014-07-10

JavaScript Promise启示录

本篇,简单实现一个promise,主要普及promise的用法。

一直以来,JavaScript处理异步都是以callback的方式,在前端开发领域callback机制几乎深入人心。在设计API的时候,不管是浏览器厂商还是SDK开发商亦或是各种类库的作者,基本上都已经遵循着callback的套路。

近几年随着JavaScript开发模式的逐渐成熟,CommonJS规范顺势而生,其中就包括提出了Promise规范,Promise完全改变了js异步编程的写法,让异步编程变得十分的易于理解。

在callback的模型里边,我们假设需要执行一个异步队列,代码看起来可能像这样:

loadImg('a.jpg', function() {
    loadImg('b.jpg', function() {
        loadImg('c.jpg', function() {
            console.log('all done!');
        });
    });
});

这也就是我们常说的回调金字塔,当异步的任务很多的时候,维护大量的callback将是一场灾难。当今Node.js大热,好像很多团队都要用它来做点东西以沾沾“洋气”,曾经跟一个运维的同学聊天,他们也是打算使用Node.js做一些事情,可是一想到js的层层回调就望而却步。

好,扯淡完毕,下面进入正题。

Promise可能大家都不陌生,因为Promise规范已经出来好一段时间了,同时Promise也已经纳入了ES6,而且高版本的chrome、firefox浏览器都已经原生实现了Promise,只不过和现如今流行的类Promise类库相比少些API。

所谓Promise,字面上可以理解为“承诺”,就是说A调用B,B返回一个“承诺”给A,然后A就可以在写计划的时候这么写:当B返回结果给我的时候,A执行方案S1,反之如果B因为什么原因没有给到A想要的结果,那么A执行应急方案S2,这样一来,所有的潜在风险都在A的可控范围之内了。

上面这句话,翻译成代码类似:

var resB = B();
var runA = function() {
    resB.then(execS1, execS2);
};
runA();

只看上面这行代码,好像看不出什么特别之处。但现实情况可能比这个复杂许多,A要完成一件事,可能要依赖不止B一个人的响应,可能需要同时向多个人询问,当收到所有的应答之后再执行下一步的方案。最终翻译成代码可能像这样:

var resB = B();
var resC = C();
...

var runA = function() {
    reqB
        .then(resC, execS2)
        .then(resD, execS3)
        .then(resE, execS4)
        ...
        .then(execS1);
};

runA();

在这里,当每一个被询问者做出不符合预期的应答时都用了不同的处理机制。事实上,Promise规范没有要求这样做,你甚至可以不做任何的处理(即不传入then的第二个参数)或者统一处理。

好了,下面我们来认识下Promise/A+规范

  • 一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)

  • 一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换

  • promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致

  • then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。

可以看到,Promise规范的内容并不算多,大家可以试着自己实现以下Promise。

以下是笔者自己在参考许多类Promise库之后简单实现的一个Promise,代码请移步promiseA

简单分析下思路:

构造函数Promise接受一个函数resolver,可以理解为传入一个异步任务,resolver接受两个参数,一个是成功时的回调,一个是失败时的回调,这两参数和通过then传入的参数是对等的。

其次是then的实现,由于Promise要求then必须返回一个promise,所以在then调用的时候会新生成一个promise,挂在当前promise的_next上,同一个promise多次调用都只会返回之前生成的_next

由于then方法接受的两个参数都是可选的,而且类型也没限制,可以是函数,也可以是一个具体的值,还可以是另一个promise。下面是then的具体实现:

Promise.prototype.then = function(resolve, reject) {
    var next = this._next || (this._next = Promise());
    var status = this.status;
    var x;

    if('pending' === status) {
        isFn(resolve) && this._resolves.push(resolve);
        isFn(reject) && this._rejects.push(reject);
        return next;
    }

    if('resolved' === status) {
        if(!isFn(resolve)) {
            next.resolve(resolve);
        } else {
            try {
                x = resolve(this.value);
                resolveX(next, x);
            } catch(e) {
                this.reject(e);
            }
        }
        return next;
    }

    if('rejected' === status) {
        if(!isFn(reject)) {
            next.reject(reject);
        } else {
            try {
                x = reject(this.reason);
                resolveX(next, x);
            } catch(e) {
                this.reject(e);
            }
        }
        return next;
    }
};

这里,then做了简化,其他promise类库的实现比这个要复杂得多,同时功能也更多,比如还有第三个参数——notify,表示promise当前的进度,这在设计文件上传等时很有用。对then的各种参数的处理是最复杂的部分,有兴趣的同学可以参看其他类Promise库的实现。

在then的基础上,应该还需要至少两个方法,分别是完成promise的状态从pending到resolved或rejected的转换,同时执行相应的回调队列,即resolve()reject()方法。

到此,一个简单的promise就设计完成了,下面简单实现下两个promise化的函数:

function sleep(ms) {
    return function(v) {
        var p = Promise();

        setTimeout(function() {
            p.resolve(v);
        });

        return p;
    };
};

function getImg(url) {
    var p = Promise();
    var img = new Image();

    img.onload = function() {
        p.resolve(this);
    };

    img.onerror = function(err) {
        p.reject(err);
    };

    img.url = url;

    return p;
};

由于Promise构造函数接受一个异步任务作为参数,所以getImg还可以这样调用:

function getImg(url) {
    return Promise(function(resolve, reject) {
        var img = new Image();

        img.onload = function() {
            resolve(this);
        };

        img.onerror = function(err) {
            reject(err);
        };

        img.url = url;
    });
};

接下来(见证奇迹的时刻),假设有一个BT的需求要这么实现:异步获取一个json配置,解析json数据拿到里边的图片,然后按顺序队列加载图片,没张图片加载时给个loading效果

function addImg(img) {
    $('#list').find('> li:last-child').html('').append(img);
};

function prepend() {
    $('<li>')
        .html('loading...')
        .appendTo($('#list'));
};

function run() {
    $('#done').hide();
    getData('map.json')
        .then(function(data) {
            $('h4').html(data.name);

            return data.list.reduce(function(promise, item) {
                return promise
                    .then(prepend)
                    .then(sleep(1000))
                    .then(function() {
                        return getImg(item.url);
                    })
                    .then(addImg);
            }, Promise.resolve());
        })
        .then(sleep(300))
        .then(function() {
            $('#done').show();
        });
};

$('#run').on('click', run);

这里的sleep只是为了看效果加的,可猛击查看demo!当然,Node.js的例子可查看这里

在这里,Promise.resolve(v)静态方法只是简单返回一个以v为肯定结果的promise,v可不传入,也可以是一个函数或者是一个包含then方法的对象或函数(即thenable)。

类似的静态方法还有Promise.cast(promise),生成一个以promise为肯定结果的promise;

Promise.reject(reason),生成一个以reason为否定结果的promise。

我们实际的使用场景可能很复杂,往往需要多个异步的任务穿插执行,并行或者串行同在。这时候,可以对Promise进行各种扩展,比如实现Promise.all(),接受promises队列并等待他们完成再继续,再比如Promise.any(),promises队列中有任何一个处于完成态时即触发下一步操作。

标准的Promise

可参考html5rocks的这篇文章JavaScript Promises,目前高级浏览器如chrome、firefox都已经内置了Promise对象,提供更多的操作接口,比如Promise.all(),支持传入一个promises数组,当所有promises都完成时执行then,还有就是更加友好强大的异常捕获,应对日常的异步编程,应该足够了。

第三方库的Promise

现今流行的各大js库,几乎都不同程度的实现了Promise,如dojo,jQuery、Zepto、when.js、Q等,只是暴露出来的大都是Deferred对象,以jQuery(Zepto类似)为例,实现上面的getImg()

function getImg(url) {
    var def = $.Deferred();
    var img = new Image();

    img.onload = function() {
        def.resolve(this);
    };

    img.onerror = function(err) {
        def.reject(err);
    };

    img.src = url;

    return def.promise();
};

当然,jQuery中,很多的操作都返回的是Deferred或promise,如animateajax

// animate
$('.box')
    .animate({'opacity': 0}, 1000)
    .promise()
    .then(function() {
        console.log('done');
    });

// ajax
$.ajax(options).then(success, fail);
$.ajax(options).done(success).fail(fail);

// ajax queue
$.when($.ajax(options1), $.ajax(options2))
    .then(function() {
        console.log('all done.');
    }, function() {
        console.error('There something wrong.');
    });

jQuery还实现了done()fail()方法,其实都是then方法的shortcut。

处理promises队列,jQuery实现的是$.when()方法,用法和Promise.all()类似。

其他类库,这里值得一提的是when.js,本身代码不多,完整实现Promise,同时支持browser和Node.js,而且提供更加丰富的API,是个不错的选择。这里限于篇幅,不再展开。

尾声

我们看到,不管Promise实现怎么复杂,但是它的用法却很简单,组织的代码很清晰,从此不用再受callback的折磨了。

最后,Promise是如此的优雅!但Promise也只是解决了回调的深层嵌套的问题,真正简化JavaScript异步编程的还是Generator,在Node.js端,建议考虑Generator。

下一篇,研究下Generator。

github原文:https://github.com/chemdemo/chemdemo.github.io/issues/6

参考文献

查看原文

chshouyu 评论了文章 · 2014-07-09

Deepin 2014 初体验

认证与成就

  • 获得 59 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2013-10-19
个人主页被 498 人浏览