在前面先说明一下,虽然经常会有人拿Promise和Ajax做比较,但是二者真的不是一个东西。Ajax用来获取后台的数据,Promise是ES6引入的,用来充当异步操作与回调函数之间的中介。
异步编程的背景知识
- 事件模型
鼠标事件或是键盘事件是Javascript中最基础的异步编程形式,直到事件触发时才执行事件处理程序,且执行时上下文与定义时的相同。
let button = document.getElementById( 'btn' );
button.onclick = function(event){
console.log("Clicked");
};
事件模型适用于处理简单、低频的交互行为,对于更复杂的需求来说不是很灵活。
- 回调模式
NodeJs通过普及回调函数来改进异步编程模型。
readFile('example.txt',function(err, contents){
if(err) throw err;
console.log("Finished");
});
console.log("Hello world!")
//输出结果如下(在不抛出错误的情况下)
Hello world!
Finished
回调模式比事件模型更灵活,但是需要添加错误处理机制,并且容易陷入回调地狱。
Promise的基础知识
Promise相当于异步操作结果的占位符,它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个Promise。
// readFile 承诺将在未来的某个时刻完成
let promise = readFile('example.txt');
这段代码中,readFile不会立即开始读取文件,函数会先返回一个表示异步读取操作的Promise对象,未来对这个对象的操作完全取决于Promise的生命周期。
Promise的生命周期
每个 Promise 都会经历一个短暂的生命周期:先是处于进行中(pending)的状态,此时操作尚未完成,所以它也是未处理的(unsettled)的;一旦异步操作执行结束,Promise 则变为已处理(settled)的状态。
在之前的例子中,当readFile函数返回Promise时它变为pending状态,操作结束后,Promise可能会进入到以下两个状态中的其中一个:
- Fulfilled Promise 异步操作成功完成
-
Rejected 由于程序错误或一些其他原因,Promise 异步操作失败。
一般我们只需要记住Promise的3种状态:“pending”、“fulfilled”及“rejected”。
但是这三种属性并不对外暴露,所以无法检测。
//所有的Promise都有then方法,接受两个参数
let promise = readFile('example.txt');
promise.then(function(contents){
// fulfilled
},function(error){
// rejected
})
创建未完成的 Promise
// let定义的promise就是未完成的promise
let promise = new Promise(function(resolve, reject){
console.log("Promise");
resolve();
})
// promise.then 是处理程序
promise.then(function(){
console.log("Resolved");
})
console.log("Hi")
// 输出结果
Promise
Hi
Resolved
完成处理程序和拒绝处理程序总是在执行器完成后被添加到任务队列的末尾。
创建已处理的 Promise
- 使用Promise.resolve()
Promise.resolve()方法只接受一个参数并返回一个完成态 的 Promise。
// let 定义的promise就是完成的promise
let promise = Promise.resolve(100);
// promise.then是处理程序
promise.then(function(value){
console.log(value);//100
})
由于该promise永远不会存在拒绝状态,因为该promise的拒绝处理程序永远不会被调用。
- 使用Promise.reject()
Promise.reject()方法只接受一个参数并返回一个拒绝态 的 Promise。
// let 定义的promise就是完成的promise
let promise = Promise.reject(222);
// promise.catch是处理程序
promise.catch(function(value){
console.log(value);//222
})
任务附加到这个Promise的拒绝程序都将被调用,但却不会调用完成处理程序。
Note: 如果像Promise.resolve()方法或是Promise.reject()方法传入一个Promise,那么这个Promise会被直接返回。
- 非 Promise 的 Thenable 对象
拥有then()方法并且接受resolve和reject这两个参数的普通对象就是非 Promise 的 Thenable 对象:
let thenable = {
then : function(resolve,reject){
resolve(100)
}
};
let p = Promise.resolve( thenable );
p.then(function(value){
console.log(value);// 100
})
执行器错误
如果执行器内部抛出一个错误,则Promise的拒绝处理程序就会被调用。
let promise = new Promise(function(resolve,reject){
throw new Error('Explosion')
})
promise.catch(function(err){
console.log(err.message); //Explosion
})
全局的 Promise 拒绝处理
有关Promise的其中一个最具争议的问题是,如果在没有拒绝处理程序的情况下拒绝一个Promise,那么不会提示失败信息,这是Javascript语言中唯一一处没有强制报错的地方。
Promise的特性决定了很难检测一个Promise是否被处理过。
Node.js 环境的拒绝处理
在Node.js中,处理Promise拒绝时会触发process对象上的两个事件:
- unhandledRejection 在一个事件循环中,当Promise被拒绝,并且没有提供拒绝处理程序时,触发该事件。
let rejected;
process.on('unhandledRejection',function(error, promise){
console.log(error.message); // Explosion
console.log(rejected == promise); // true
})
rejected = Promise.reject(new Error('Explosion'));
- rejectionHandled 在一个事件循环后,当Promise被拒绝时,若拒绝程序被调用,触发该事件。
let rejected;
process.on('rejectionHandled',function( promise){
console.log(rejected == promise); // true
})
rejected = Promise.reject(new Error('Explosion'));
// 等待添加拒绝处理程序
setTimeout(function(){
rejected.catch(function(error){
console.log(error.message); // Explosion
})
},1000)
这里的 rejectionHandled 事件在拒绝处理程序最后被调用时触发,如果在创建rejected 之后直接添加拒绝处理程序,那么rejectionHandled事件不会被触发,因为rejected创建的过程与拒绝处理程序的调用在同一个事件循环中,此时rejectionHandled事件尚未生效。
浏览器环境的拒绝处理程序
浏览器也是通过触发两个事件来识别未处理的拒绝的,虽然这些事件是在window对象上触发的,但实际上与Node.js中的完全等效。
-
unhandledrejection 在一个事件循环中,当Promise被拒绝,并且没有提供拒绝处理程序时,触发该事件。
-
rejectionhandled 在一个事件循环后,当Promise被拒绝时,若提供拒绝处理程序被调用时,触发该事件。
在浏览器中,事件处理程序接受一个有以下属性的事件对象作为参数:
- type 事件名称( unhandledrejection 和 rejectionhandled )
- promise 被拒绝的promise对象
- reason 来自Promise的拒绝值
let rejected
window.onunhandledrejection = function(event){
console.log(event.type); // 'unhandledrejection'
console.log(event.reason.message); // 'Explosion'
console.log(rejection === event.promise ); // true
};
window.onrejectionhandled = function(event){
console.log(event.type); //'rejectionhandled'
console.log(event.reason.message); //'Explosion'
console.log(rejected === event.promise); // true
}
rejected = Promise.reject(new Error('Explosion'))
串联 Promise
- 捕获错误
let p1 = new Promise(function(resolve, reject){
resolve(666)
});
p1.then(function(value){
throw new Error('Boom')
}).catch(function(error){
console.log(error.message); // Boom
})
-
Promise链的返回值
Promise链的另一个重要特性是可以给下游Promise传递数据
let p1 = new Promise(function(resolve, reject){
resolve(666)
})
p1.then(function(value){
console.log(value) // 666
return value+1
}).then(function(value){
console.log(value) //667
})
拒绝处理程序中也可以做相同的事情,在必要时,即使其中一个Promise失败也能恢复整条链的执行。
- 在Promise链中返回Promise
let p1 = new Promise(function(resolve, reject){
resolve(1)
})
let p2 = new Promise(function(resolve, reject){
resolve(2)
})
p1.then(function(value){
//第一个完成处理程序
console.log(value) //1
return p2
}).then(function(value){
// 第二个完成处理程序
console.log(value) //2
})
响应多个Promise
- Promise.all() 方法只接受一个参数并返回一个Promise,该参数是一个含有多个受监视Promise的可迭代对象,只有当可迭代对象中所有Promise都被解决后返回的Promise才会被解决,只有当可迭代对象中所有Promise都被完成后返回的Promise才会被完成。
let p1 = new Promise(function(resolve, reject){
resolve(1)
})
let p2 = new Promise(function(resolve, reject){
resolve(2)
})
let p3 = new Promise(function(resolve, reject){
resolve(3)
})
let p4 = Promise.all([p1,p2,p3])
p4.then(function(value){
console.log(Array.isArray(value)); //true
console.log(value); // [1,2,3]
})
所有传入Promise.all()方法的Promise只要有一个被拒绝,那么返回的Promise没等所有的Promise都完成就立即被拒绝。
- Promise.race() 方法也接受含多个受监视Promise的可迭代对象作为唯一参数并返回一个Promise,但只要有一个Promise被解决返回的Promise就被解决,无须等到所有的Promise都被完成。
let p1 = Promise.resolve(1)
let p2 = new Promise(function(resolve, reject){
resolve(2)
})
let p3 = new Promise(function(resolve, reject){
resolve(3)
})
let p4 = Promise.race([p1,p2,p3])
p4.then(function(value){
console.log(value); // 1
})
实际上,传给Promise.race()方法的Promise会进行竞选,以决出哪一个先被解决,如果先解决的是已完成Promise,则返回已完成Promise;如果先解决的是已拒绝Promise,则返回已拒绝Promise。
自Promise继承
Promise与其他内建类型一样,也可以作为基类派生其他类。
class MyPromsie extends Promise {
// 使用默认的构造函数
success(resolve, reject){
return this.then(resolve, reject)
}
failure(reject){
return this.catch(reject)
}
}
let promise = new MyPromise(function(resolve, reject){
resolve(1)
})
promise.success(function(value){
console.log(value) // 1
}).failure(function(value){
console.log(value)
})
基于Promise的异步任务执行
... 持续更新中 ...
网友评论