promise
是干什么的
在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,然而在需要多次回调嵌套的时候,就容易进入回调地狱
了,promise
解决了这一问题
Promise
是异步编程的一种解决方案,比传统的解决方案–回调函数和事件--更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了语法,原生提供了Promise
。所谓Promise
,简单说就是一个容器,里面保存着某个未来才回结束的事件(通常是一个异步操作)的结果。从语法上说,Promise
是一个对象,从它可以获取异步操作的消息。 Promise
对象的状态不受外界影响
promise
是什么样的
promise
对象上的方法
Promise.all()
Promise.race()
Promise.reject()
Promise.resolve()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.prototype.then()
复制代码
- 三种状态:
promise
只有三种状态
pending 进行中
fulfilled 已经成功
rejected 已经失败
复制代码
- 状态改变:
Promise
对象的状态改变,只有两种可能:
pending => fulfilled
pending => rejected
复制代码
基本用法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例
const p = new Promise(function(resolve,reject){
if(/*异步操作成功*/){
resolve(value);
}else{
reject(error);
}
})
复制代码
resolve
函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending
变为 resolved
),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; reject
函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending
变为 rejected
),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise
实例生成以后,可以用then 方法分别指定resolved
状态和rejected
状态的回调函数。
p.then(function(value){
//success
},function(error){
//failure
});
复制代码
原理解析
针对以上的基本用法,我们深入解析一下Promise
的实现原理:
首先promsie
有三种状态,它是个构造函数,且接收一个函数作为参数,这个函数我们称之为excutor
,函数里有两个参数resolve和reject
,这两个回掉的作用我们在基本用法里已经说了
跟jQuery链式调用相似,promise
的链式调用是在 Promise.prototype.then()
和 Promise.prototype.catch()
里返回了promise
实例 这张图清楚的描述了promise
链式调用的过程
- 构造函数
const PENDING = 'pengding'; // 初始态
const FULFILLED = 'fulfilled';// 成功态
const REJECTED = 'rejected';// 成功态
function Promise(excutor) {
let self = this;
self.status = PENDING; // 设置状态
// 定义存放成功回调的数组
self.onResolveCallbacks = [];
// 定义存放失败回调的数组
self.onRejectCallbacks = [];
// 当调用此方法的时候,如果promise状态为pending时可以以转成成功态,如果已经是成功或者失败则什么都不做
function resolve(value) {
if (self.status == PENDING){ // 如果是初始态则转成成功态
self.status = FULFILLED;
self.value = value; // 成功后会得到一个值且不能改变
self.onResolveCallbacks.forEach(cb => cb(self.value)) // 调用所有成功的回调
}
}
function reject(reason) {
if(self.status == PENDING){ // 如果是初始态则转成失败态
self.status = REJECTED;
self.value = reason;
self.onRejectCallbacks.forEach(cb=>cb(self.value))
}
}
try{
// excutor函数执行可能会异常,需要捕获,如果出错需要用这个错误对象reject
excutor(resolve,reject)
}catch (e) {
// excutor执行失败需要用失败原因reject这个promise
reject(e)
}
}
复制代码
then
方法 在基本用法中我们知道在调用then
方法时传进去的是两个函数,分别对应的是promise
中异步方法成功和失败时的回调
Promise.prototype.then = function (onFulfilled, onRejected) {
// 如果成功和失败的回调没传,表示这个then没任何逻辑,只会把值往后抛
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value=>value;
onRejected = typeof onRejected == 'function' ? onRejected : reason=>{ throw reason};
let self = this;
let promise2;
// 为了实现链式调用,每一种状态都要返回的是一个promise实例
if(self.status == FULFILLED){ //如果promise状态已经是成功态了,onFulfilled直接取值
return promise2 = new Promise(function (resolve,reject) {
setTimeout(function () { // 保证返回的promise是异步
try{
onFulfilled(self.value);
}catch (e) {
// 如果执行成功的回调过程中出错,用错误原因把promise2 reject
reject(e)
}
})
});
}
if(self.status == REJECTED){
return promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try{
onRejected(self.value);
}catch(e){
reject(e)
}
})
})
}
if(self.status == PENDING){
return promise2 = new Promise(function (resolve, reject){
// pending状态时把所有的回调函数都添加到实例的两个堆栈中暂存,等状态改变后依次执行,其实这个过程就是观察者模式
self.onResolveCallbacks.push(function () {
setTimeout(function () {
try{
onFulfilled(self.value);
}catch(e){
reject(e)
}
})
});
self.onRejectCallbacks.push(function () {
setTimeout(function () {
try{
onRejected(self.value);
}catch(e){
reject(e)
}
})
})
});
}
};
复制代码
以上只是支持了一次then
的调用,而现实中我们会有这种需求
const p = new Promise(function(resolve,reject){
if(/*异步操作成功*/){
resolve(value);
}else{
reject(error);
}
});
p.then(function(){
//success
}).then(function(){
//success
}).then(function(){
//success
}).catch(function(e){
//failure
})
复制代码
这种连续链式调用then
方法,连续返回promise
实例的情况,而且我们要兼容then
方法里返回的不是promise
对象,这要求对then
优化,加入一个解析promise
的方法resolvePromise
。这样我们就会遇到三种情况:
返回值是
promise
实例返回值是个
thenable
对象或者函数没有返回值或者只返回一个普通值
先看一下什么是
thenable
对象
const thenable = {
// 所谓 thenable 对象,就是具有 then 属性,而且属性值是如下格式函数的对象
then: (resolve, reject) => {
resolve(200)
}
}
复制代码
//resolvePromise
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){
return reject(new TypeError('循环引用'))
}
let called = false;// 是否resolve或reject被调用,这两个回调只能被调用一次
if(x instanceof Promise){
if(x.status === PENDING){
x.then(function (y) { // 深度递归
resolvePromise(promise2,y,resolve,reject)
},reject)
}else{
x.then(resolve,reject)
}
}else if(x != null && ((typeof x =='object') || (typeof x == 'function'))){
// x 是个thenable对象或函数
try{
let then = x.then;
if(typeof then == 'function'){
then.call(x,function (y) {
// 如果promise2已经成功或者失败了就不要再调用了
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject)
},function (err) {
if(called) return;
called = true;
reject(err)
})
}else{
// 到此的话x不是个thenabe对象,直接把它当成值 resolve promise2就可以了
resolve(x)
}
}catch (e) {
if(called) return;
called = true;
reject(e)
}
}else{
// 如果x是个普通值,则用x的值去resolve promise2
resolve(x)
}
}
复制代码
优化后的then
方法
Promise.prototype.then = function (onFulfilled, onRejected) {
// 如果成功和失败的回调没传,表示这个then没任何逻辑,只会把值往后抛
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value=>value;
onRejected = typeof onRejected == 'function' ? onRejected : reason=>{ throw reason};
let self = this;
let promise2;
if(self.status == FULFILLED){ //如果promise状态已经是成功态了,onFulfilled直接取值
return promise2 = new Promise(function (resolve,reject) {
setTimeout(function () {
try{
let x = onFulfilled(self.value);
if(x instanceof Promise){
// 如果获取到返回值X,会走解析promise的过程
resolvePromise(promise2,x,resolve,reject)
}
}catch (e) {
// 如果执行成功的回调过程中出错,用错误原因把promise2 reject
reject(e)
}
})
});
}
if(self.status == REJECTED){
return promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try{
let x = onRejected(self.value);
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
})
}
if(self.status == PENDING){
return promise2 = new Promise(function (resolve, reject){
self.onResolveCallbacks.push(function () {
setTimeout(function () {
try{
let x = onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
});
self.onRejectCallbacks.push(function () {
setTimeout(function () {
try{
let x = onRejected(self.value);
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
})
});
}
};
复制代码
catch
方法
catch
原理就是只传失败的回调
Promise.prototype.catch = function(onReject){
this.then(null,onReject);
};
复制代码
- 最后导出这个类就可以了
try {
module.exports = Promise
} catch (e) {
console.log(e)
}
复制代码
实际项目中的应用---封装一个异步加载图片的方法
function imgLoad(url) {
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'blob';
request.onload = function() {
if (request.status === 200) {
resolve(request.response);
} else {
reject(Error('Image didn\'t load successfully; error code:' + request.statusText));
}
};
request.onerror = function() {
reject(Error('There was a network error.'));
};
request.send();
});
}
var body = document.querySelector('body');
var myImage = new Image();
imgLoad('XXX.jpg').then(function(response) {
var imageURL = window.URL.createObjectURL(response);
myImage.src = imageURL;
body.appendChild(myImage);
}, function(Error) {
console.log(Error);
});
复制代码