前言
本文将从浅到深的去剖析promise。由于内容较多,分为上下两篇。
内容大纲
- 异步编程的解决方案(完成)
- promise是什么(完成)
- promise的用法(完成)
- promise的优点与缺点(完成)
- promise的错误捕获(待续)
- promise的原理(待续)
- promise的底层源码(待续)
- 自己实现一个promise(待续)
异步编程的解决方案
js是一门单线程的语言,所以其中会涉及到很多异步的操作,异步编程的解决方案有很多种,我们主要讲一种最基础的和这篇文章的主角(promise):
回调函数
在异步编程中,这种的应用范围最广,举个定时器的例子:
setTimeout(() => {
// ...
}, 1000);
当然在业务中异步请求也会用到很多,但当多个异步操作需要串行操作的时候,就会有回调地狱产生。
$.get(url, data1 => {
console.log(data1)
$.get(data1.url, data2 => {
console.log(data2)
$.get(data2.url, data3 => {
console.log(data3)
})
})
})
代码没有美感,且不利于维护。当然,我们可以通过减少代码嵌套,模块化等手段来修复。但是并不如下面的解决方案优雅。
佼佼者——promise
const reqMethod = (url) => {
return new Promise((reslove, reject) => {
$.get(url, data => {
if(data.success) {
reslove();
} else {
reject();
}
})
})
}
reqMethod(url).then((data1) => {
return reqMethod(data1.url);
}).then((data2) => {
return reqMethod(data2.url);
}).then((data3) => {
return reqMethod(data3.url);
})
这样的实现方式,符合易于阅读,因为每一步操作都是按照先后顺序进行的。
promise是什么
Promise从不同角度理解,有很多种含义。
promise的一种承诺
promise从字面意思理解,就是许诺和承诺的意思,对于一种承诺而言,有三种状态:
1、承诺还未达成,还在纠结过程中(pending状态)
2、承诺没有实现,失言了(rejected状态)
3、承诺实现了,就是成功的状态(fulfilled状态)
Promise是一种标准的规范
在这里不过多展开,大家可以去看Promise/A+ 规范或者ECMAscript规范;
Promise是ES6提供的一种对象
Promise是一个对象,是一个构造函数,ES6将其写进了语言标准。统一了用法。最基础的用法如下:
const promiseMethod = new Promise((resolve, reject) => {
// some code
if(/*异步成功的条件*/) {
resolve();
} else {
reject();
}
})
Promise的用法
new Promise
Promise是一个构造函数,最基础的作用就是用new操作符生成一个实例对象
const promiseMethod = new Promise((resolve, reject) => {
// some code
if(/*异步成功的条件*/) {
resolve();
} else {
reject();
}
})
Promise
可接受的参数是一个函数,resolve
和reject
是该函数的两个参数,由js引擎提供,不需要自己定义。
resolve
的作用是将pending
状态变更为fulfilled
状态。
reject
的作用是将pending
状态变更为rejected
状态。
Promise新建时就会立即执行,与何时调用无关,与结果也无关
举个例子
const promiseOne = new Promise((resolve, reject) => {
console.log('has finish');
resolve();
})
1、console.log
在新建过程中就执行了。
2、promiseOne
的结果已经固定下来了,无论何时调用,结果都不会发生改变。
Promise.prototype.then
then
方法是被定义在Promise
的原型上,作用是:添加Promise
状态改变后的回调函数。
then
方法接收两个参数,第一个参数是resolve
状态执行的函数,第二个参数是reject
执行的函数。
then
方法返回的是一个新的Promise
对象。
举个例子:
const promiseOne = new Promise((resolve, reject) => {
console.log('has finish');
resolve();
})
const promiseTwo = new Promise((resolve, reject) => {
console.log('has reject');
reject();
})
const promiseThree = promiseOne.then(() => {
console.log('成功的回调')
}, () => {
console.log('失败的回调')
})
promiseTwo.then(() => {
console.log('成功的回调')
}, () => {
console.log('失败的回调')
})
console.log(promiseThree);
输出的结果:
has finish
has reject
<Promise>(pending)
成功的回调
失败的回调
Promise.prototype.catch
catch
方法其实和then
第二个回调函数的别名,作用是用于储物发生时的回调处理。
catch
捕获两类错误:
1、异步操作时抛出错误,导致状态变为reject
2、then
回调过程中产生的错误。
举个例子:
//第一种情况:异步操作过程中报错
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// 第二种情况:then执行过程中报错
const promise = new Promise((resolve, reject) => {
resolve();
})
promise.then(() => {
throw new Error('test');
}).catch(function(error) {
console.log(error);
});
关于catch
和then
第二个参数的区别:
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
第二种写法的优点在于,then执行过程中的报错,catch一样能捕获,优于第一种写法。
更深入的错误捕获我们单独放一章讲。
Promise.prototype.finally
finally
方法作用在于不管promise
返回的状态是什么,它都会在执行。
finally
不接受任何参数。
promise
.then(function(data) {
// success
})
.catch(function(err) {
// error
}).finally(() => {
});
finally
中执行的状态与promise
的结果无关,而且在方法中无法得知promise
的运行结果。
Promise.all
用法:
Promise.all([p1, p2, p3]);
作用:是将多个promise
示例封装成一个promise
实例。结果只有以下两种情形:
- 全部成功
所有promise
都成功,总的promise
才会成功
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
const p2 = new Promise((resolve, reject) => {
resolve('hello');
})
Promise.all([p1, p2]).then(() => {
console.log('全部成功')
}).catch(() => {
console.log('全部失败')
})
// 全部成功
- 全部失败
只要有一个promise
的状态从pending
变成reject
就是失败。
const p1 = new Promise((resolve, reject) => {
resolve();
})
const p2 = new Promise((resolve, reject) => {
reject();
})
Promise.all([p1, p2]).then(() => {
console.log('全部成功')
}).catch(() => {
console.log('全部失败')
})
Promise的时间如何计算?
const newDate = new Date();
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 500)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000)
})
Promise.all([p1, p2]).then(() => {
const now = new Date();
let time = now.getTime() - newDate.getTime();
console.log(time);
})
// 1001
按最长的那个为准。
Promise.race
用法:
Promise.race([p1, p2, p3]);
作用:是将多个promise
示例封装成一个promise
实例。
Promise.race与Promise.all的区别
Promise.race
的状态取决于最先完成的promise
的状态。
举个例子,我们需要对一个请求做5秒的timeout,就可以用Promise.race
。
const reqMethod = (url) => {
return new Promise((reslove, reject) => {
$.get(url, data => {
reslove();
})
})
}
const timeout = new Promise((resolve, reject) => {
setTimeout(() => {
reject('timeout')
}, 5000)
})
Promise.race([reqMethod(xxx),timeout]).then(() => {
console.log('请求成功')
}).catch(() => {
console.log('timeout')
})
Promise.resolve
作用:需要将现有的对象转化为promise
对象。
Promise.resolve({});
//等价于
new Promise(resolve => resolve({}));
Promise.resolve会根据传入的不同参数做不同的处理
- 传入一个promise对象
不做任何操作,原封不动的返回该对象。
- 参数是一个thenable对象
立即执行thenable
对象中的then
方法,然后返回一个resolved
状态的promise
。
let thenable = {
then: function(resolve, reject) {
resolve('success')
}
}
const p = Promise.resolve(thenable);
p.then((e) => {
console.log(e);
})
- 不是对象
直接返回一个resolved
状态的promise
。并将参数带给回调函数。
const p = Promise.resolve('参数');
p.then((e) => {
console.log(e)
})
- 不传参数
直接返回一个resolved
状态的promise
。
Promise.reject
其作用是返回一个新的promise
实例,状态直接为reject
Promise.reject('error');
//等价于
new Promise((resolve, reject) => reject('error'));
不同于Promise.resolve
,其参数会原封不动的作为reject
的理由。
举个例子:
const p = Promise.reject('error');
p.catch((e) => {
console.log(e)
})
// error
Promise的优缺点
Promise的特点
- 状态不受外界影响,只满足于结果
1、promise代表一个异步操作,一共有三种状态:pending、fulfilled和rejected。
2、promise的结果只服从于异步操作的结果,成功进入fulfilled状态,失败进入rejected状态。
- 状态变更之后不会在改变
1、promise状态变化只有两种可能,一种从pending到fulfilled,或者是从pending到reject。
2、当状态变更完时,状态将不在发生改变。
Promise的优点
- 链式写法可读性比回调函数的写法更优雅。
- 与回调函数相比更加方便错误处理。
- 与事件体系相比,一次性返回结果,更加适用于一次性返回的结果。
Promise的缺点
- 不能取消执行过程。
- 不能读取异步过程中的进度。
- 生成之后对象结果不会改变。
结语
本文对promise的用法进行了详解,之后会更新两篇深入一点的文章:
- promise错误捕捉
- 聊聊promise系列(深入)