写在前面
假设现在一个日常开发会遇到这样一个需求:多个接口异步请求,第二个接口依赖于第一个接口执行完毕之后才能利用数据进行一系列操作。一般会这样写:
A.fetchData({
url: 'http://......',
success: function (data) {
A.fetchData({
// 要在第一个请求成功后才可以执行下一步
url: 'http://......',
success: function (data) {
// ......
}
});
}
});
这样写没问题,但是有两个缺点:
1、当有多个操作的时候,会导致多个回调函数嵌套,不够美观
2、如果有几个操作没有前后顺序之分时,例如上面的后一个请求不依赖与前一个请求的返回结果的时候,同样也需要等待上一个操作完成再实行下一个操作。
从ES6开始,Promise对象可以解决上述问题。
什么是Promise对象
一个Promise对象可以理解为一次将要执行的操作,使用了Promise对象之后可以用一种链式调用的方式来组织代码,让代码更加直观。
resolve和reject
先看代码:
function helloWorld (ready) {
return new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello World!");
} else {
reject("Good bye!");
}
});
}
helloWorld(true).then(function (message) {
alert(message);
}, function (error) {
alert(error);
});
上面的代码实现的功能非常简单,helloWord 函数接受一个参数,如果为 true 就打印 "Hello World!",如果为 false 就打印错误的信息。helloWord 函数返回的是一个 Promise 对象。
在 Promise 对象当中有两个重要方法————resolve 和 reject。
resolve 方法可以使 Promise 对象的状态改变成成功,同时传递一个参数用于后续成功后的操作,在这个例子当中就是 Hello World! 字符串。
reject 方法则是将 Promise 对象的状态改变为失败,同时将错误的信息传递到后续错误处理的操作。
then
Promise 对象有三种状态:
1.Fulfilled 可以理解为成功的状态
2.Rejected 可以理解为失败的状态
3.Pending 既不是 Fulfilld 也不是 Rejected 的状态,可以理解为 Promise 对象实例创建时候的初始状态。
helloWorld 的例子中的 then方法就是根据 Promise 对象的状态来确定执行的操作,resolve 时执行第一个函数(onFulfilled),reject 时执行第二个函数(onRejected)。promise模式在任何时刻都处于以下三种状态之一:未完成(unfulfilled)、已完成(resolved)和拒绝(rejected)。以CommonJS Promise/A 标准为例,promise对象上的then方法负责添加针对已完成和拒绝状态下的处理函数。then方法会返回另一个promise对象,以便于形成promise管道,这种返回promise对象的方式能够支持开发人员把异步操作串联起来,如then(resolvedHandler, rejectedHandler); 。resolvedHandler 回调函数在promise对象进入完成状态时会触发,并传递结果;rejectedHandler函数会在拒绝状态下调用。
示例代码1:
function printHello (ready) {
var promise = new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello");
} else {
reject("Good bye");
}
});
return promise;
}
function printWorld () {
console.log('World');
}
function printExclamation () {
console.log('!!!');
}
printHello(true)
.then(function(message)
.then(printWorld)
.then(printExclamation)
.catch(function(error){
console.log(error);
});;
函数先执行printHello,返回一个promise对象,通过then将异步操作串联起来。
执行结果应该是 Hello
World
!!!
示例代码2:
function helloWorld (ready) {
return new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello World!");
} else {
reject("Good bye!");
}
});
}
var _this = this;
printHello(true)
.then(function (message) {
var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
return fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
var movie = responseData.movies[1];
console.log('data = ', movie.title);
return movie.title;
})
},function (error) {
return(error);
}).then(function (message) {
return message + ' World';
}).then(function (message) {
return message + '!!!';
}).then(function (message) {
console.log(message);
console.log('finally');
}).catch(function(error){
console.log(error);
});
上面的代码中有两个promise,第一个promise执行完毕后也就是printHello之后,会执行下一个then,这个then返回了一个获取数据的promise,后面的then拿到的promise都是指向这个promise对象的。上述例子通过链式调用的方式,按顺序打印出了相应的内容。then 可以使用链式调用的写法原因在于,每一次执行该方法时总是会返回一个 Promise 对象。另外,在 then onFulfilled 的函数当中的返回值,可以作为后续操作的参数。
catch
catch 方法是 then(onFulfilled, onRejected) 方法当中 onRejected 函数的一个简单的写法,也就是说可以写成 then(fn).catch(fn),相当于 then(fn).then(null, fn)。使用 catch 的写法比一般的写法更加清晰明确。
新手使用容易犯的错误
1.忘记添加catch()方法
这是一个很常见的错误。很多程序员对他们代码中的promise调用十分自信,觉得代码永远不会抛出一个 error ,也可能他们只是简单的忘了加 catch() 方法。不幸的是,不加 catch() 方法会让回调函数中抛出的异常被吞噬,在你的控制台是看不到相应的错误的,这对调试来说是非常痛苦的。
为了避免这种糟糕的情况,我已经养成了在自己的promise调用链最后添加如下代码的习惯:
somePromise().then(function () {
return aPromise();
}).then(function () {
return anotherPromise();
}).catch(function(error){
console.log(error);
});
即使你并不打算在代码中处理异常,在代码中添加 catch() 也是一个谨慎的编程风格的体现。在某种情况下你原先的假设出错的时候,这会让你的调试工作轻松一些。
2.return的混淆乱用
在then方法内部,我们可以做三件事:
1.return 一个promise对象
2.return一个同步的值或者是 undefined
3.同步的 throw 一个错误
理解这三种情况之后,你就会理解promise了。
1.返回另一个promise对象
在有关promise的相关文章中,这种写法很常见,就像上文提到的构成promise链的一段代码:
getUserByName('nolan').then(function (user) {
return fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
}
}).then(function () {
});
2.返回一个具体的值或者是 undefined
getUserByName('nolan').then(fcuntion (user) {
if (inMemoryCache[user.id]) {
return inMemoryCache[user.id];
// returning a value!
}
return inMemoryCache[user.id];
// returning a promise
}).then(function (userAccount) {
// I got a user account
})
如果不调用return语句的话,javaScript里的函数会返回 undefined 。这也就意味着在你想返回一些值的时候,不显式调用return会产生一些副作用。
出于上述原因,养成了一个个人习惯就是在then方法内部永远显式的调用return或者throw。我也推荐你这样做。
3.抛出一个错误
说到throw,这又体现了promise的功能强大。在用户退出的情况下,我们的代码中会采用抛出异常的方式进行处理:
getUserByName('nolan').then(function (user) {
if (user.isLoggedOut()) {
throw new Error('user logged out!');
// throwing a error!
}
if (inMemoryCache[user.id]) {
return inMemoryCache[user.id];
// returning a value!
}
return getUserAccountById(user.id);
// returning a promise!
}).then(function (userAccount) {
// I got a user account!
}).catch(function (err) {
// Boo, I got an error!
});
如果用户已经登出的话, catch() 会收到一个错误,如果有promise对象的状态变为rejected的话,它还会收到一个错误。
在使用promise的时候抛出异常在开发阶段很有用,它能帮助我们定位代码中的错误。比方说,在then函数内部调用 JSON.parse() ,如果JSON对象不合法的话,可能会抛出异常,在回调函数中,这个异常会被吞噬,但是在使用promise之后,我们就可以捕获到这个异常了。
微博账号:梅嘉庆(点击关注)