在javascript中,代码是单线程执行的,对于一些比较耗时的IO操作,都是通过异步回调函数来实现的。
但是这样会存在一个问题,当下一个的操作需要上一个操作的结果时,我们只能把代码嵌到上一个操作的回调函数里,这样一层嵌一层,最终形成回调地狱。
$.get('/login.php', function (login) { $.get('/user.php', function (user) { $.get('/info.php', function (info) { //代码就这样一层嵌一层,不够直观,维护也麻烦 }); }); });
为了解决这种问题,ES6中就提供了Promise方法来解决这种问题。
Promise是一个构造函数,通过它,我们可以创建一个Promise实例对象。
let p = new Promise(function (resolve, reject) { setTimeout(() => { console.log('OK'); resolve('OK'); }, 1000); });
Promise构造函数接受一个函数作为参数,这个函数有两个参数,resolve和reject。
resolve函数是将Promise的状态设置为fulfilled(完成),reject函数是将Promise的状态设置为rejected(失败)。
上述代码,我们并没有进行任何调用,当运行时,间隔1秒后输出了'OK'。所以这里需要注意,我们通常使用Promise时,需要在外层再包裹一层函数。
let p = function () { return new Promise(function (resolve, reject) { setTimeout(() => { console.log('OK'); resolve('OK'); }, 1000); }); }; p();
上面的代码p();返回的是一个Promise实例对象,Promise对象上有 then() , catch() , finally() 方法。
then方法有两个参数,onFulfilled和onRejected,都是函数。
onFulfilled用于接收resolve方法传递过来的数据,onRejected用于接收reject方法传递过来的数据。
let p = function () { return new Promise(function (resolve, reject) { setTimeout(() => { if (Math.random() > 0.5) { resolve('OK'); } else { reject('ERR'); } }, 1000); }); }; p().then(function (data) { console.log('fulfilled', data); }, function (err) { console.log('rejected', err); });
then()方法总是会返回一个Promise实例,这样我们就可以一直调用then()。
在then方法中,你既可以return 一个具体的值 ,还可以return 一个Promise对象。
如果直接return的是一个数据,那then方法会返回一个新Promise对象,并以该数据进行resolve。
let p = function () { return new Promise(function (resolve, reject) { resolve(1); }); }; p().then(function (data) { console.log(`第 ${data} 次调用`); //注意这里直接返回的值 //then会创建一个新的Promise对象,并且以返回的值进行resolve //那么该值会被下面的then方法的onFulfilled回调拿到 return ++data; }).then(function (data) { console.log(`第 ${data} 次调用`); return ++data; }).then(function (data) { console.log(`第 ${data} 次调用`); return ++data; });
如果返回的是一个Promise对象,请看下面代码。
let p = function () { return new Promise(function (resolve, reject) { resolve(1); }); }; p().then(function (data) { console.log(`第 ${data} 次调用`); return new Promise(function (resolve, reject) { resolve(++data); }); }).then(function (data) { console.log(`第 ${data} 次调用`); return new Promise(function (resolve, reject) { resolve(++data); }); }).then(function (data) { console.log(`第 ${data} 次调用`); return new Promise(function (resolve, reject) { resolve(++data); }); });
其实效果与直接返回值的是一样的。
即然then()可以进行链式操作,那我们最早之前的回调地狱写法,就可以通过它进行改进了。
function login() { return new Promise(function (resolve, reject) { $.get('/login.php', function (result) { resolve(result); }); }); } function user(data) { return new Promise(function (resolve, reject) { $.get('/user.php', function (result) { resolve(result); }); }); } function info(data) { return new Promise(function (resolve, reject) { $.get('/info.php', function (result) { resolve(result); }); }); } login().then(function (data) { console.log('处理login'); //把login异步处理获取的数据,传入到下一个处理中。 return user(data); }).then(function (data) { console.log('处理user'); //把user异步处理获取的数据,传入到下一个处理中。 return info(data); }).then(function (data) { console.log('处理info'); });
这样修改后,回调地狱层层嵌套的结构就变的清晰多了。上述代码是伪代码。
Promise对象还有一个catch方法,用于捕获错误,该方法与 then(null, onRejected) 等同,是一个语法糖。
let p = function () { return new Promise(function (resolve, reject) { resolve('开始'); }); }; p().then(function (data) { console.log('1'); return new Promise(function (resolve, reject) { reject('错误1'); }); }).then(function (data) { console.log('2'); return new Promise(function (resolve, reject) { reject('错误2'); }); }).then(function (data) { console.log('3'); return new Promise(function (resolve, reject) { reject('错误3'); }); }).catch(function (reason) { console.log(reason); });
注意,一旦操作中有错误发生,则会进入到catch中,后面的操作将不再执行。
Promise对象内部自带了try catch,当代码运行时错误,会自动以错误对象为值reject,并最终被catch捕获。