Promise对象的理解

一.介绍

Promise是异步编程的一种解决方案,它有三种状态,分别是pending-进行中、resolved-已完成、rejected-已失败。比传统的解决方案(回调函数和事件)更合理更强大。

所谓Promise,简单说就是一个容器里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息

Promise对象有以下2个特点:

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。
这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

有了Promise对象,就可以把异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供了统一的接口(resolved统一只指fulfilled状态,不包含rejected状态。),使得控制异步操作更加容易。

Promise也有一些缺点。 首先, 无法取消Promise, 一旦新建它就会立即执行, 无法中途取消。 其次, 如果不设置回调函数, Promise内部抛出的错误,不会反应到外部。 第三, 当处于Pending状态时, 无法得知目前进展到哪一个阶段( 刚刚开始还是即将完成)。
如果某些事件不断地反复发生, 一般来说, 使用 stream 模式是比部署Promise更好的选择。

二.基本用法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例

// 方法1
let promise = new Promise ( (resolve, reject) => {   
	if ( success ) {        
		resolve(a) // pending ——> resolved 参数将传递给对应的回调方法    
	} else {        
		reject(err) // pending ——> rejectd   
	}
})
// 方法2
function promise () {    
return new Promise ( function (resolve, reject) {        
			if ( success ) { 
				resolve(a)        
			} else {            
				reject(err)        
			}    
		} 
	)}

注意:实例化的Promise对象会立即执行

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,又JavaScript引擎提供,不是自己部署。

resolve函数的作用,将Promise对象的状态从“未完成”变成“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; 

reject函数的作用是,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

这个Promises对象有一个then方法,允许指定回调函数,在异步任务完成后调用

可以用then方法分别制定Resolved状态和Rejected状态的回调函数:
promise.then(function(value){
  // sucess
},function(error){
  // failure
});

then方法可以接受2个回调函数作为参数,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

下面是一个Promise对象的简单例子:

let promise = new Promise(function(resolve,rejeact){
  console.log('Promise');
  resolve(data);//
});

promise.then(function(data){
  console.log('Resolved');//后面可以用传过来的数据data做些其他操作
  console.log(data);
});

// Promise
// Resolved
上面代码中,Promise新建后立即执行,所以首先输出的是”Promise”,
然后then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以”Resolved”最后输出。

三.Promise API

1.Promise.prototype.then()

       Promise 实例具有then方法, 也就是说, then方法是定义在原型对象 Promise.prototype 上的。 它的作用是为 Promise 实例添加状态改变时的回调函数。
       前面说过, then方法的第一个参数是 Resolved 状态的回调函数, 第二个参数( 可选) 是 Rejected 状态的回调函数。

then方法返回的是一个新的 Promise 实例( 注意, 不是原来那个 Promise 实例)。 因此可以采用链式写法, 即then方法后面再调用另一个then方法。

       采用链式的then, 可以指定一组按照次序调用的回调函数。 这时, 前一个回调函数, 有可能返回的还是一个 Promise 对象( 即有异步操作), 这时后一个回调函数, 就会等待该 Promise 对象的状态发生变化, 才会被调用。

getJSON("/post/1.json").then(function(post) {  
    return getJSON(post.commentURL);  
}).then(function funcA(comments) {  
    console.log("Resolved: ", comments);  
}, function funcB(err) {  
    console.log("Rejected: ", err);  
});  


如果采用箭头函数, 上面的代码可以写得更简洁。
getJSON("/post/1.json").then(  
    post => getJSON(post.commentURL)  
).then(  
    comments => console.log("Resolved: ", comments),  
    err => console.log("Rejected: ", err)  
);  
上面代码中, 第一个then方法指定的回调函数, 返回的是另一个 Promise 对象。 这时, 第二个then方法指定的回调函数, 就会等待这个新的 Promise 对 象状态发生变化。 如果变为 Resolved, 就调用funcA, 如果状态变为 Rejected, 就调用funcB。

 2.Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection) 的别名, 用于指定发生错误时的回调函数。

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});

上面代码中, getJSON方法返回一个 Promise 对象, 如果该对象状态变为Resolved, 则会调用then方法指定的回调函数; 如果异步操作抛出错误, 状态就会变为Rejected, 就会调用catch方法指定的回调函数, 处理这个错误。 另外, then方法指定的回调函数, 如果运行中抛出错误, 也会被catch方法捕获。

var promise = new Promise(function(resolve, reject) {  
    resolve('ok');  
    throw new Error('test');  
});  
promise  
    .then(function(value) {  
        console.log(value)  
    })  
    .catch(function(error) {  
        console.log(error)  
    });  
// ok  

catch()的作用是捕获Promise的错误,与then()的rejected回调作用几乎一致。但是由于Promise 对象的错误具有“ 冒泡” 性质, 会一直向后传递, 直到被捕获为止。 也就是说, 错误总是会被下一个catch语句捕获。catch()总能够捕获then()中抛出的错误,所以建议不要使用then()的rejected回调,而是统一使用catch()来处理错误(具体解释请参考 沅一峰 ECMAScript 6 入门)

var someAsyncThing = function() {  
    return new Promise(function(resolve, reject) {  
        //  下面一行会报错,因为 x 没有声明  
        resolve(x + 2);  
    });  
};  
someAsyncThing().then(function() {  
    return someOtherAsyncThing();  
}).catch(function(error) {  
    console.log('oh no', error);  
    //  下面一行会报错,因为 y 没有声明  
    y + 2;  
}).catch(function(error) {  
    console.log('carry on', error);  
});  
// oh no [ReferenceError: x is not defined]  
// carry on [ReferenceError: y is not defined]  
上面代码中, 第二个catch方法用来捕获, 前一个catch方法抛出的错误。

同样,catch()中也可以抛出错误,由于抛出的错误会在下一个catch中被捕获处理,因此可以再添加catch()

3.Promise.resolve()

需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。

	var jsPromise = Promise.resolve($.ajax('/whatever.json'));  

上面代码将 jQuery 生成的deferred对象, 转为一个新的 Promise 对象。
Promise.resolve等价于下面的写法。
	Promise.resolve('foo')  
		//  等价于  
	new Promise(resolve => resolve('foo'))  	

Promise.resolve方法的参数分成四种情况

(1) 参数是一个 Promise 实例: 原样返回

如果参数是 Promise 实例, 那么Promise.resolve将不做任何修改、 原封不动地返回这个实例。

(2) 参数是一个thenable 对象 :   转换为Promise后立即执行then方法

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 方法的对象, 或根本就不是对象  :  返回resolved状态的Promise

如果参数是一个原始值, 或者是一个不具有then方法的对象, 则Promise.resolve方法返回一个新的 Promise 对象, 状态为Resolved。

var 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方法。

var 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‘) 则是立即执行, 因此最先输出。

4.Promise.all()

Promise.all方法用于将多个 Promise 实例, 包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

var promise = Promise.all( [p1, p2, p3] )
promise.then(
    ...
).catch(
    ...
)

如下面列子:
//  生成一个 Promise 对象的数组  
var promises = [2, 3, 5, 7, 11, 13].map(function(id) {  
    return getJSON("/post/" + id + ".json");  
});  
Promise.all(promises).then(function(posts) {  
    // ...  
}).catch(function(reason) {  
    // ...  
});  

上面代码中, Promise.all方法接受一个数组作为参数, p1、 p2、 p3都是 Promise 对象的实例, 如果不是, 就会先调用下面讲到的Promise.resolve方法, 将参数转为 Promise 实例, 再进一步处理。( Promise.all方法的参数可以不是数组, 但必须具有 Iterator 接口, 且返回的每个成员都是 Promise 实例。)
p的状态由p1、 p2、 p3决定, 分成两种情况。
( 1) 只有p1、 p2、 p3的状态都变成fulfilled, p的状态才会变成fulfilled, 此时p1、 p2、 p3的返回值组成一个数组,会传递给p(调用then()的已完成回调)的回调函数

( 2) 只要p1、 p2、 p3之中有一个被rejected, p的状态就变成rejected, 此时第一个被reject的实例的返回值, 会传递给p的回调函数。

5.Promise.race()

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

var promise = Promise.race( [p1, p2, p3] )
promise.then(
    ...
).catch(
    ...
)
上面代码中, 只要p1、 p2、 p3之中有一个实例率先改变状态, p的状态就跟着改变。 那个率先改变的 Promise 实例的返回值, 就传递给p的回调函数。
Promise.race方法的参数与Promise.all方法一样, 如果不是 Promise 实例, 就会先调用下面讲到的Promise.resolve方法, 将参数转为 Promise 实例,再进一步处理

下面是一个例子, 如果指定时间内没有获得结果, 就将 Promise 的状态变为reject, 否则变为resolve。

var p = Promise.race([  
    fetch('/resource-that-may-take-a-while'),  
    new Promise(function(resolve, reject) {  
        setTimeout(() => reject(new Error('request timeout')), 5000)  
    })  
])  
p.then(response => console.log(response))  
p.catch(error => console.log(error))  
上面代码中, 如果 5 秒之内fetch方法无法返回结果, 变量p的状态就会变为rejected, 从而触发catch方法指定的回调函数。

6.Promise.reject()

Promise.reject(reason) 方法也会返回一个新的 Promise 实例, 该实例的状态为rejected。 它的参数用法与Promise.resolve方法完全一致。

var p = Promise.reject(' 出错了 ');  
//  等同于  
var p = new Promise((resolve, reject) => reject(' 出错了 '))  
p.then(null, function(s) {  
    console.log(s)  
});  
//  出错了  

上面代码生成一个 Promise 对象的实例p, 状态为rejected, 回调函数会立即执行。

7.Promise.prototype.finally()

finally方法接受一个方法作为参数,这个方法不管promise最终的状态是怎样,都一定会被执行。它与done方法的最大区别, 它接受一个普通的回调函数作为参数, 该函数不管怎样都必须执行。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

8.Promise.done() 

Promise 对象的回调链, 不管以then方法或catch方法结尾, 要是最后一个方法抛出错误, 都有可能无法捕捉到( 因为 Promise 内部的错误不会冒泡到全局)。 因此, 我们可以提供一个done方法, 总是处于回调链的尾端, 保证抛出任何可能出现的错误。

Promise.done() 的用法类似 .then() ,可以提供resolved和rejected方法,也可以不提供任何参数,它的主要作用是在回调链的尾端捕捉前面没有被 .catch() 捕捉到的错误

Promise.prototype.done = function(onFulfilled, onRejected) {  
    this.then(onFulfilled, onRejected)  
        .catch(function(reason) {  
            //  抛出一个全局错误  
            setTimeout(() => {  
                throw reason  
            }, 0);  
        });  
};  

从上面代码可见, done方法的使用, 可以像then方法那样用, 提供Fulfilled和Rejected状态的回调函数, 也可以不提供任何参数。 但不管怎样, done都会捕捉到任何可能出现的错误, 并向全局抛出。


三.demo 

1. 简单登录判断

		//是否登录
		let checkLogin = function(){
			return new Promise(function(resolve,reject){
				//flag是否登录成功  
				let flag =document.cookie.indexOf("userid")>-1?true:false;
				if(flag=true){
					resolve({
						status:0,
						result:true
					})
				}else{
					reject("err");
				}
			})
		};
		//用户信息
		let getUser = () =>{
			return new Promise((resolve,reject) =>{
				let userInfo={
					userid:"1002"
				}
				resolve(userInfo)
			})
		}
		checkLogin().then((res) =>{
			if(res.status == 0){
				console.log("login success");
			}
		}).catch((error)=>{
			console.log( `error:${error}` );
		});

		Promise.all([checkLogin(),getUser()]).then(([res1,res2]) => {
			console.log( `输出res1:${res1.result},输出res2:${res2.userid}`);//输出res1:true,输出res2:1002
		}).catch((error)=>{
			console.log( `error:${error}` );
		})

2.加载图片

我们可以将图片的加载写成一个Promise, 一旦加载完成, Promise的状态就发生变化。

const preloadImage = function(path) {  
    return new Promise(function(resolve, reject) {  
        var image = new Image();  
        image.onload = resolve;  
        image.onerror = reject;  
        image.src = path;  
    });  
};  
3. Generator 函数与 Promise 的结合

使用 Generator 函数管理流程, 遇到异步操作的时候, 通常返回一个Promise对象。

function getFoo() {  
    return new Promise(function(resolve, reject) {  
        resolve('foo');  
    });  
}  
var g = function*() {  
    try {  
        var foo = yield getFoo();  
        console.log(foo);  
    } catch(e) {  
        console.log(e);  
    }  
};  
  
function run(generator) {  
    var it = generator();  
  
    function go(result) {  
        if(result.done) return result.value;  
        return result.value.then(function(value) {  
            return go(it.next(value));  
        }, function(error) {  
            return go(it.throw(error));  
        });  
    }  
    go(it.next());  
}  
run(g);  
上面代码的 Generator 函数g之中, 有一个异步操作getFoo, 它返回的就是一个Promise对象。 函数run用来处理这个Promise对象, 并调用下一个next方法。

你可能感兴趣的:(ES6)