前段时间的工作中,由于项目要在前端实现存储,于是便使用了websql,而websql的API涉及到了很多的异步问题,如果采取回调函数的方式处理,代码不够优雅,而且不利于理解,于是便找到了Promise,使用之后有一些自己的理解和心得,跟大家在本文中一起分享一下。
Promise为何物?
Promise中文释义为“誓言”、“承诺”之意,根据其音译,那就是“普罗米修斯”,这货很强大啊,在希腊神话中,是最具智慧的神明之一,最早的泰坦巨神后代,名字有“先见之明”(Forethought)的意思。
而JS中的Promise,其实就是ES6提供的一个对象,它扮演的就是“先知”这么个角色,它用来传递异步操作的消息,代表了某个未来才会知道结果的事件,并且这个事件提供统一的API,便于进一步处理,这个类目前在chrome32、Opera19、Firefox29以上的版本都已经支持。
接下来说说Promise规范,大家应该都不陌生了,因为这个规范已经出来很长一段时间了,目前在chrome32、Opera19、Firefox29以上的版本都已经支持Promise了,Promise规范的主要内容如下:
- Promise对象有三种状态:Pending(进行中)、Resolved(已完成)、Rejected(已失败),这三种状态只能从Pending转到Rejected或者Pending转到Rejected,不能逆向转换,而且状态一旦转换完成后,状态就凝固了,不能够再改变了。只有异步操作的结果,才能决定当前是哪一种状态,任何其他操作都无法改变这个状态,这点跟Deferred是不一样的。
- Promise模式唯一需要的一个接口是调用then方法,它可以用来注册当promise完成或者失败时调用的回调函数,then方法接受两个参数,第一个参数是成功时的回调,在promise由Pending态转换到Resolved态时调用,另一个是失败时的回调,在promise由Pending态转换到Rejected态时调用。
Promise有啥好处?
说了这么多,Promise到底有啥好处呢?能为我们的工作带来什么便捷呢?话不多说,是骡子是马拉出来遛遛,我们先new一个来玩玩:
var p = new Promise(function(resolve, reject){ //用setTimeout模拟异步操作 setTimeout(function(){ console.log('第一个Promise执行完成'); resolve('第一个Promise'); }, 2000); });
上面的代码可以看出,new一个Promise对象时,要传入一个函数,而这个函数一般有两个参数,一个是resolve,另一个是reject,这两个参数不一定都要,但你要用的那个一定要传入,所以两个都写上就好了,这两个参数其实代表的是异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。而上面这段代码的意思就是用setTimeout模仿一个异步的操作,2秒后,在控制台打印“第一个Promise执行完成”,然后调用resolve方法,带一个参数:“第一个Promise”,运行代码,会直接输出“第一个Promise执行完成”,虽然我们只是new了一个Promise对象,但是函数当参数传进去的时候会被立即执行,所以直接输出,为了防止这种情况,我们一个将这段代码包在一个函数中:
function test1(){ var p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.log('第一个Promise执行完成'); resolve('第一个Promise'); }, 2000); }); return p; }
如上,最后返回了一个Promise对象,接下来重头戏来了,Promise对象的then方法要出场了,它可以用来注册当Promise完成或者失败时调用的回调函数,我们接着上面的代码继续往下写:
test1().then(function(val){ console.log(val); //后续操作 })
执行这段代码,两秒后,会在页面输出“第一个Promise执行完成”,紧接着输出“第一个Promise”。到这里,机智的你已经看出来,这个then不就是异步处理完成后,将传递过来的数据取到,然后进行后续操作,就是充当一个回调函数嘛,感觉这个Promise没有什么大不了的地方啊,只不过是把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数而已,其实这种链式调用在一些比较复杂的页面中是很有必要的,因为回调函数如果比较少还好说,一旦多了起来而且必须讲究执行顺序的话,回调函数开始嵌套,那代码的恶心程度是简直无法忍受。而Promise的出现就是为了解决这个问题的。
Promise就提供了一种优雅的解决方案,主要用法就是将各个异步操作封装成好多Promise,而一个Promise只处理一个异步逻辑。最后将各个Promise用链式调用写法串联,在这样处理下,如果异步逻辑之间前后关系很重的话,你也不需要层层嵌套,只需要把每个异步逻辑封装成Promise链式调用就可以了。
Promise的基本API
话不多说,先用console.dir(Promise)打印出来看看:
从上图可以看出来主要有以下几个方法:
- Resolve:该方法可以使 Promise 对象的状态改变成成功,同时传递一个参数用于后续成功后的操作。
- Reject:该方法则是将 Promise 对象的状态改变为失败,同时将错误的信息传递到后续错误处理的操作。
- Then: 所有的 Promise 对象实例里都有一个 then 方法,它是用来跟这个 Promise 进行交互的,then方法主要传入两个方法作为参数,一个 resolve 函数,一个 reject 函数,链式调用 ,上一个Promise对象变为resolved的时候,调用then中的Resolve方法,否则调用Reject方法,且then 方法会缺省调用 resolve() 函数。
- Catch:该方法是 then(onFulfilled, onRejected) 方法当中 onRejected 函数的一个简单的写法,也就是说也可以写成then,但是用来捕获异常时,用catch更加便于理解。
- All:该方法可以接收一个元素为 Promise 对象的数组作为参数,当这个数组里面所有的 Promise 对象都变为 resolve 时,该方法才会返回。就是全部都执行完了才接着往下执行,例如:
var p1 = new Promise(function (resolve) { setTimeout(function () { resolve("p1"); }, 2000); }); var p2 = new Promise(function (resolve) { setTimeout(function () { resolve("p2"); }, 2000); }); Promise.all([p1, p2]).then(function (result) { console.log(result); });
6. Race:竞速,类似All方法,它同样接收一个数组,不同的是只要该数组中的 Promise 对象的状态发生变化(无论是 resolve 还是 reject)该方法都会返回。就是只要某一个执行完了就接着往下执行。
Promise的链式调用
Then的链式调用才是Promise的意义所在,而要实现链式调用,在then中的resolve方法如何return很关键。在then方法中通常传递两个参数,一个 resolve 函数,一个 reject 函数。reject暂时不讨论,就是出错的时候运行的函数罢了。resolve 函数必须返回一个值才能把链式调用进行下去,而且这个值返回什么是有很大讲究的。
-
在then的resolve方法中返回一个新的Promise对象,如下代码:
function test1(){ var p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.log('第一个Promise执行完成'); resolve('第一个Promise传来的值'); }, 2000); }); return p; } test1().then(function(val){ console.log('进入第一个then'); console.log(val); var p1 = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('第一个then执行完成'); resolve('第二个Promise传来的值'); }, 2000); }) return p1; }).then(function(val){ console.log('进入第二个then'); console.log(val); })
打印结果如下:
上述例子通过链式调用的方式,按顺序打印出了相应的内容。then 可以使用链式调用的写法原因在于,每一次执行该方法时总是会返回一个 Promise 对象,后一个then中的resolve方法要等到前一个then处理完了才执行。而返回一个新Promise之后再调用的then就是新Promise中的逻辑了。另外,在 then onFulfilled 的函数当中的返回值,可以作为后续操作的参数。
-
在then的resolve方法中返回一个值,如下代码:
function test1(){ var p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.log('第一个Promise执行完成'); resolve('第一个Promise传来的值'); }, 2000); }); return p; } test1().then(function(val){ console.log('进入第一个then'); console.log(val); setTimeout(function(){ console.log('第一个then执行完成'); }, 2000); return '第二个Promise传来的值'; }).then(function(val){ console.log('进入第二个then'); console.log(val); })
打印结果如下:
可以看出,第一个then中的resolve还未执行完,就进入了第二个then中,没有进行等待,是因为第一个then中返回的是一个字符串,而不是Promise对象,返回的值会传递到下一个then的resolve方法参数中。
总结
以上就是ES6 Promise的基本概念和用法,Promise还是非常赞的,如果你还在使用回调模式,我强烈建议你切换到 Promise,这样你的代码会变的更少,更优雅,并且更加容易理解,这样就不会等你开发完后,被那些读你代码的人在背后偷偷骂你。本文我自己理解结合网上的一些资料,希望你看完之后对你有帮助,有不足之处欢迎指正,谢谢~