ES6 Promise 对象及解决回调地狱问题
概述
在JavaScript的世界中,所有代码都是单线程执行的。
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。
AJAX就是典型的异步操作。
把回调函数success(request.responseText)
和fail(request.status)
写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。
有没有更好的写法?比如写成这样:
var ajax = ajaxGet('http://...'); ajax.ifSuccess(success) .ifFail(fail);
先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success
函数或fail
函数。这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。
实例
我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:
function test(resolve, reject) { var timeOut = Math.random() * 2; console.log('set timeout to: ' + timeOut + ' seconds.'); setTimeout(function () { if (timeOut < 1) { console.log('call resolve()...'); resolve('200 OK'); } else { console.log('call reject()...'); reject('timeout in ' + timeOut + ' seconds.'); } }, timeOut * 1000); }
可以看出,test()
函数只关心自身的逻辑,并不关心具体的resolve
和reject
将如何处理结果。
有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:
var p1 = new Promise(test); var p2 = p1.then(function (result) { console.log('成功:' + result); }); var p3 = p2.catch(function (reason) { console.log('失败:' + reason); });
Promise对象可以串联起来,所以上述代码可以简化为:
new Promise(test).then(function (result) { console.log('成功:' + result); }).catch(function (reason) { console.log('失败:' + reason); });
可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:
Promise.all() 这两个任务是可以并行执行的
var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, 'P1'); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); // 同时执行p1和p2,并在它们都完成后执行then: Promise.all([p1, p2]).then(function (results) { console.log(results); // 获得一个Array: ['P1', 'P2'] });
Promise.race() 多个异步任务是为了容错,只需要获得先返回的结果即可。
var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, 'P1'); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); Promise.race([p1, p2]).then(function (result) { console.log(result); // 'P1' });
Promise 状态
Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。
Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。
then 方法
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
通过 .then 形式添加的回调函数,不论什么时候,都会被调用。
回调函数 VS Promise
之前大家解决异步事件都是用回调函数去解决,但是回调函数写异步会出现一些问题,
回调地狱的问题。
那么promise就是一种新的处理异步的方法,可以完美的解决回调函数处理异步带来的问题(回调地狱问题)。
其实promise就是异步操作的方法,就像之前的定时器一样,看到定时器就知道这是异步操作,同样,看到promise就知道这个是异步操作。
var sayhello = function (name, callback) { setTimeout(function () { console.log(name); callback(); }, 1000); } sayhello("first", function () { sayhello("second", function () { sayhello("third", function () { console.log("end"); }); }); }); //输出: first second third end
解决回调地狱有很多方法,比如:Promise 对象、Generator 函数、async 函数
Promise 对象解决回调地狱
var sayhello = function (name) { return new Promise(function (resolve, reject) { setTimeout(function () { console.log(name); resolve(); //在异步操作执行完后执行 resolve() 函数 }, 1000); }); } sayhello("first").then(function () { return sayhello("second"); //仍然返回一个 Promise 对象 }).then(function () { return sayhello("third"); }).then(function () { console.log('end'); }).catch(function (err) { console.log(err); })
另外一个实例
function f1(){ return new Promise(resolve=>{ setTimeout(()=>{ console.log('第一步'); //异步逻辑已经执行完,必须要告诉外界我执行完了 resolve(); },1000) }) } function f2(){ return new Promise(resolve=>{ setTimeout(()=>{ console.log('第二步'); //告诉外界我执行完了 resolve(); },1000) }) } f1().then(res=>{ return f2(); }).then(res=>{ return f1(); }).then(res=>{ return f2(); }).then(res=>{ setTimeout(()=>{ console.log('完成'); },1000) })