promise的使用方法
异步操作作为JavaScript中的一大特色,解决了JavaScript单线程运行的一个难题,但是很多时候有问题也在于这上面。最大的问题之一,就是异步操作过多的时候,代码内会充斥着众多回调函数,乃至形成回调金字塔。为了解决回调函数带来的问题,Promise作为一种更优雅的异步解决方案被提出,最初只是一种实现接口规范,而到了es6,则是在语言层面就原生支持了Promise对象。
-
Promise语法
promise的核心编程思想就是如果数据就绪(promised),那么(then)做点什么。
来看一下下面这个promise的使用示例
const promise = new Promise(function(resolve, reject) { // ... some code let randNumber = Math.random(); if (randNumber <0.5){ resolve(randNumber ); } else { reject('出错了'); } }); promise.then(data=>{ console.log(data); }).catch(error=>{ console.log(error); })
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
resolve函数的作用:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为rejected), 在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。其中如果执行了resolve则进入then方法,如果执行reject则进入catch方法,相当于是出了异常。
当然也不一定要写catch方法,在then里面可以接收两个参数,第二个参数就处理reject返回的信息。
promise.then(function(value) { // success }, function(error) { // failure });
下面是一个使用then的例子。then方法返回的是一个新的Promise实例。 因此可以采用链式写法,即then方法后面再调用另一个then方法。
getJSON("/sendMs").then(function(json) { return json.post; }).then(function(post) { // ... });
上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
上面代码中,getJSON方法返回一个 Promise 对象;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。
从上面的代码中我们还可以看出一个点,就是如果reject和resolve方法带有参数,则会将参数传递后相应的回调函数中。当然,这些值可以是任意类型的,有一种特殊的类型就是参数是一个promise对象。
const p1 = new Promise(function (resolve, reject) { // ... }); const p2 = new Promise(function (resolve, reject) { // ... resolve(p1); })
在上面代码中,p1和p2都是promise实例对象,而p2的resolve函数将p1作为参数传入,即一个异步操作的结果返回的是另一个异步操作。
这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
在使用promise的过程中需要注意的有以下几点:
- 调用resolve或reject并不会终结 Promise 的参数函数的执行。调用完成以后,后面的代码依然会执行,只不过再抛出错误,不会被捕获,等于没有抛出,后面的代码执行对于传入的参数没有任何影响。因为 Promise的状态一旦改变,就永久保持该状态,不会再变了。
- 如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。
-
Promise.resolve()
有时需要将现有对象转为 Promise 对象,以便于链式的使用then方法,Promise.resolve方法就起到这个作用。
Promise.resolve等价于下面的写法。
Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo'))
Promise.resolve方法的参数分成四种情况。
1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
2)参数是一个thenable对象
thenable对象指的是具有then方法的对象,比如下面这个对象。
let thenable = { then: function(resolve, reject) { resolve(42); } };
Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 });
上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42。
3)参数不是具有then方法的对象,或根本就不是对象。
如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) }); // Hello
上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。
4)不带有任何参数
Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法。
const p = Promise.resolve(); p.then(function () { // ... });
上面代码的变量p就是一个 Promise 对象。需要注意的是,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three
上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise. resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。
文章参考链接:https://juejin.im/post/5af29a62f265da0b8f628973