原文地址javascript
Promise是针对异步编程的一种解决方案,可以抽象处理异步对象以及对其进行操做。Promise并非Javascript语言的扩展,只是在ES6中将其写进了语言标准,原生Javascript提供了Promise对象,统一了用法。前端
Promise对象存在三种状态:pending、fulfilled、rejected。其中pending 状态最终必定会变为fulfilled或者是rejected中的一种,且不会再发生改变。整个过程由Promise机制保证不会受到外界干扰。当状态由pending->fulfilled时执行已完成的回调函数,由pending->rejected时,调用失败的回调函数。一旦状态改变,就不会再被修改而一直保持这个状态。java
实例方法git
静态方法github
你们常说Javascript是单线程的语言,这当然是对的。这里的单线程是说,在JS引擎中负责解释和执行JavaScript代码的线程只有一个,每一个特定的时刻只有特定的代码被执行,并阻塞其余代码。换句话说,JavaScript是单线程,可是浏览器是多线程的。除了负责解析JS代码以外,还存在其余线程,负责HTTP请求,DOM处理等等。编程
同步能够理解成在一个函数返回时,我就拿到了须要的结果,无需等待。异步则是函数返回时,我没有获得应该获得的结果,并且要在将来某一时间才能获得。程序执行的顺序和任务执行的顺序并不始终保持一致。promise
常见的操做有接口请求,文件读取,定时器操做等。在浏览器端耗时很长的操做,最好都异步执行。(鼠标点击事件、窗口大小拖拉事件等也能够算是异步的)浏览器
promise对象的状态,只有对象处于pending状态时是可变的,一旦从Pending转换为Fulfilled或Rejected以后, 这个promise对象的状态就不会再发生任何变化。也就是说,Promise与Event等不一样,在.then 后执行的函数能够确定地说只会被调用一次,并且只会执行fulfilled或者rejected中的一个。多线程
通常状况下会使用new Promise建立对象,除此以外,也可使用其余方法。异步
静态方法Promise.resolve(value) 能够认为是new Promise()方法的快捷方式,同理Promise.reject(error)也是。
thenable对象和promise对象并非一回事,thenable对象能够理解为具备.then方法的对象。
能够经过Promise.resolve将thenable对象转化为promise对象。
promise的链式调用能够经过使用.then处理回调函数比较多的状况,必定程度上解决回调地狱的问题。链式调用的原理(后续能够单独介绍下)简单来讲是Promise内部有一个defers队列存放事件,程序执行时.then将下个事件放入,promise状态变化结束以后出发响应函数执行,而后将事件从队列中移除。由于每一次调用.then时都会建立一个全新的promise对象,因此保证了链式调用能够顺利执行。
demo1
前一个task的返回值做为后一个task的参数。不使用链式调用,直接调用.then方法时,因为每次都是新建的promise对象,因此value的值始终为100。
// 1: 对同一个promise对象同时调用 then 方法 var aPromise = new Promise(function (resolve) { resolve(100); }); aPromise.then(function (value) { return value * 2; }); aPromise.then(function (value) { return value * 2; }); aPromise.then(function (value) { console.log("1: " + value); // => 100 }) // vs // 2: 对 then 进行 promise chain 方式进行调用 var bPromise = new Promise(function (resolve) { resolve(100); }); bPromise.then(function (value) { return value * 2; }).then(function (value) { return value * 2; }).then(function (value) { console.log("2: " + value); // => 100 * 2 * 2 });
在链式调用过程当中,每个.then调用时候都会建立一个全新的promise对象,.then调用结束时会执行对应状态的回调函数,
可是若是没有传入rejected的回调,或者.catch使用的位置不一样,都会致使最后的结果不一样。
demo2
task所有正常执行:
function taskA() { console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejected(error) { console.log("Catch Error: A or B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask);
输出结果为:
Task A Task B Final Task
当taskA执行失败时:
function taskA() { console.log("Task A"); throw new Error("throw Error @ Task A") }
输出结果为:
Task A Error: throw Error @ Task A Final Task
因为taskB中并无注册rejected函数,因此taskB并未执行,而是直接经过catch捕获到了失败。
若是在taskB的then中传入了失败的回调:
promise .then(taskA) .then(taskB,function(error) { console.log('b-catch-error') }) .catch(onRejected) .then(finalTask);
执行结果为:
Task A b-catch-error Final Task
因此,在多个.then链式调用时,不一样位置报错会致使task执行顺序不一样,执行不一样,也许不符合预期。
因此,能够在自行使用try-catch,保证.then执行不会出错,或者使用其余异步编程方式。
demo3
下面代码中每一次返回的promise对象都不是同一个对象。
var aPromise = new Promise(function (resolve) { resolve(100); }); var thenPromise = aPromise.then(function (value) { console.log(value); }); var catchPromise = thenPromise.catch(function (error) { console.error(error); }); console.log(aPromise !== thenPromise); // => true console.log(thenPromise !== catchPromise);// => true
理解了这个再来看下面的问题。
demo4 错误的调用
function badAsyncCall() { var promise = Promise.resolve(); promise.then(function() { // 任意处理 return 1; }); return promise; }
demo5 正确的调用
function anAsyncCall() { var promise = Promise.resolve(); return promise.then(function() { // 任意处理 return 1; }); }
返回结果
demo4中return 的promise并非执行过.then的promise,因此并无返回预期的结果。在实际开发中要注意这个问题。
但看这个字面上可能会有些疑惑,实际上仍是概念上的问题。
使用promise.then(Fulfilled, Rejected)的话,在Fulfilled 中发生异常的话,在Rejected中是捕获不到这个异常的。缘由是promise状态只能从pending变成fulfilled或者是rejected,且变化完成以后不会再被修改。因此一旦执行了fulfilled,说明promise成功了,此时就已经不会再执行rejected了。
demo6
function onReady(fn) { var readyState = document.readyState; if (readyState === 'interactive' || readyState === 'complete') { fn(); } else { window.addEventListener('DOMContentLoaded', fn); } } onReady(function () { console.log('DOM fully loaded and parsed'); }); console.log('==Starting==');
若是这段代码在源文件中出现的位置不一样,在控制台上打印的log消息顺序也会不一样。 结果不可控这是极力要避免的。
正确的写法应该是:
function onReadyPromise() { return new Promise(function (resolve, reject) { var readyState = document.readyState; if (readyState === 'interactive' || readyState === 'complete') { resolve(); } else { window.addEventListener('DOMContentLoaded', resolve); } }); } onReadyPromise().then(function () { console.log('DOM fully loaded and parsed'); }); console.log('==Starting==');
为了不上述中同时使用同步、异步调用可能引发的混乱问题,Promise在规范上规定 Promise只能使用异步调用方式 。
(后续学习再补充)