Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。ECMAscript 6 原生提供了 Promise 对象。而且ES7中的async/await也是Promise基础实现的。Promise到底有魅力和作用呢?本文将解开它的面纱,探索promise的原理及用法。不用再被一些回调地狱、各种异步调用所头疼烦恼。
什么是Promise?
promise,是承诺的意思。在JavaScript中promise指一个的对象或函数(是一个包含了兼容promise规范then方法的对象或函数)。 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
- promise的特点
1-1、 Promise的三种状态
- pending: Promise对象实例创建时候的初始状态
- resolved:可以理解为成功的状态
- rejected:可以理解为失败的状态
- 如果是pending状态,则promise:可以转换到resolved或rejected状态。
- 如果是resolved状态,则promise:不能转换成任何其它状态。必须有一个值,且这个值不能被改变。
- 如果是rejected状态,则promise可以:不能转换成任何其它状态。必须有一个原因,且这个值不能被改变。
”值不能被改变”指的是其identity不能被改变,而不是指其成员内容不能被改变。
1-3、promise 有一个 then 方法,就是用来指定Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数 (onFulfilled),reject 时执行第二个函数(onRejected)
promiseFn().then(resolve(onFulfilled){
//当promise状态变成fulfilled时,调用此函数
},reject(onRejected){
//当promise状态变成rejected时,调用此函数
});复制代码
-
resolve,reject 都是可选参数
- 如果
onFulfilled
不是一个函数,则忽略之。 - 如果
onRejected
不是一个函数,则忽略之。
- 如果
onFulfilled
是一个函数:
- 它必须在
promise
fulfilled后调用, 且promise
的value为其第一个参数。 - 它不能在
promise
fulfilled前调用。 - 不能被多次调用。
- 它必须在
- 如果
onRejected
是一个函数,
- 它必须在
promise
rejected后调用, 且promise
的reason为其第一个参数。 - 它不能在
promise
rejected前调用。 - 不能被多次调用。
- 它必须在
onFulfilled
和onRejected
只允许在 execution context 栈仅包含平台代码时运行.onFulfilled
和onRejected
必须被当做函数调用 (i.e. 即函数体内的this
为undefined
).- 对于一个
promise
,它的then方法可以调用多次.
- 当
promise
fulfilled后,所有onFulfilled
都必须按照其注册顺序执行。 - 当
promise
rejected后,所有OnRejected
都必须按照其注册顺序执行。
- 当
then
必须返回一个promise .promise2 = promise1.then(onFulfilled, onRejected); 复制代码
- 如果
onFulfilled
或onRejected
返回了值x
, 则执行Promise 解析流程[[Resolve]](promise2, x)
. - 如果
onFulfilled
或onRejected
抛出了异常e
, 则promise2
应当以e
为reason
被拒绝。 - 如果
onFulfilled
不是一个函数且promise1
已经fulfilled,则promise2
必须以promise1
的值fulfilled. - 如果
OnReject
不是一个函数且promise1
已经rejected, 则promise2
必须以相同的reason被拒绝.
- 如果
2、为什么使用promise?有什么好处呢?
- 2-1 对于回调函数: 可以解决回调地狱,如下图: 例如,使用jQuery的ajax多次向后台请求数据时,并且每个请求之间需要相互依赖,则需要回调函数嵌套来解决而形成“回调地狱”。
$.get(url1, data1 => { console.log(data1,"第一次请求"); $.get(data1.url, data2 => { // 第一次请求后的返回url 在此请求后台 console.log(data2,"第二次请求") ..... }) }) 复制代码
这样一来,在处理越多的异步逻辑时,就需要越深的回调嵌套,
复制代码
这种编码模式的问题主要有以下几个:
- 代码逻辑书写顺序与执行顺序不一致,不利于阅读与维护。
- 异步操作的顺序变更时,需要大规模的代码重构。
- 回调函数基本都是匿名函数,bug 追踪困难。
- 回调函数是被第三方库代码(如上例中的 ajax )而非自己的业务代码所调用的,造成了 IoC 控制反转。
- 结果不能通过return返回
Promise 怎么解决呢?
let p = url1 => {
return new Promise((resolve, reject) => {
$.get(url, data => {
resolve(data)
});
})
};
//
p(url).then(resvloe => {
return p(resvloe.url);
}).then(resvloe2 => {
return resvloe2(resvloe2.url);
}).then(resvloe3 => {
console.log(resvloe3);
}).catch(err => throw new Error(err));
当第一个then中返回一个promise,会将返回的promise的结果,传递到下一个then中。
这就是比较著名的链式调用了。 复制代码
- 2-2、Promise 也有一些缺点。
首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
3、Promise 使用
- 1. 创建Promise。要想创建一个 promise 对象、可以使用 new 来调用 Promise 的构造器来进行实例化。接收一个excutor执行函数作为参数, excutor有两个函数类型形参resolve reject。
let promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});
复制代码
- 2.promise中的状态变化。
2. 当调用resolve(成功),会由pending => resolved
3. 当调用reject(失败),会由pending => rejected
- 3. promise对象方法 then方法
// onFulfilled 是用来接收promise成功的值
// onRejected 是用来接收promise失败的原因
// then方法是异步的
promise.then(onFulfilled, onRejected);复制代码
3-2. resolve(成功): onFulfilled会被调用
let promise = new Promise((resolve, reject) => {
resolve('fulfilled'); // 状态由 pending => fulfilled
});
promise.then(result => { // onFulfilled
console.log(result); // 'fulfilled'
}, reason => { // onRejected 不会被调用
})
复制代码
3-3.reject(失败) onRejected会被调用
let promise = new Promise((resolve, reject) => {
reject('rejected'); // 状态由 pending => rejected
});
promise.then(result => { // onFulfilled 不会被调用
}, reason => { // onRejected
console.log(reason); // 'rejected'
})
复制代码
3-4.promise.catch 方法:捕捉错误
(catch 方法是 promise.then(null, rejection) 的别名,用于指定发生错误时的回调函数。)复制代码
promise.catch(onRejected)
相当于
promise.then(null, onRrejected);
// 注意
// onRejected 不能捕获当前onFulfilled中的异常
promise.then(onFulfilled, onRrejected);
// 可以写成:
promise.then(onFulfilled)
.catch(onRrejected); 复制代码
3-5、promise chain 方法每次调用 都返回一个新的promise对象 所以可以链式写法
Promise的静态方法
function step1() {
console.log("step1");
}
function step2() {
console.log("step2");
}
function onRejected(error) {
console.log("错误方法:", error);
}
var promise = Promise.resolve();
promise
.then(step1)
.then(step2)
.catch(onRejected) // 捕获前面then方法中的异常复制代码
4、Promise的方法是使用
- Promise.resolve 返回一个fulfilled状态的promise对象
Promise.resolve('成功');
// 相当于
let promise = new Promise(resolve => {
resolve('成功');
});
复制代码
//库中实现
Promise.resolve = function (val) { return new Promise((resolve, reject) => resolve(val))}
复制代码
2.Promise.reject 返回一个rejected状态的promise对象
var p = Promise.reject('出错了');
p.then(null, function (s){
console.log(s)
});
// 出错了
复制代码
3.Promise.all 接收一个promise对象数组为参数
只有全部为resolve才会调用 通常会用来处理 多个并行异步操作
const p1 = new Promise((resolve, reject) => {
resolve(1);
});
const p2 = new Promise((resolve, reject) => {
resolve(2);
});
const p3 = new Promise((resolve, reject) => {
reject(3);
});
Promise.all([p1, p2, p3]).then(data => {
console.log(data); // [1, 2, 3] 结果顺序和promise实例数组顺序是一致的
}, err => {
console.log(err);
});
复制代码
//库中实现
Promise.all = function(arr) {
return new Promise((resolve, reject) => {
let num = 0,innerArr = [];
function done(index,data){
innerArr[index] = data;
num ++;
if(num === arr.length){
resolve(innerArr);
} }
for(let i =0 ;i<arr.length;i++){
arr[i].then((res)=>{
done(i,res);
},reject); // 有一个失败 就返回
}
})}
复制代码
4.Promise.race 接收一个promise对象数组为参数
Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。
function timerPromisefy(delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
var startDate = Date.now();
Promise.race([
timerPromisefy(10),
timerPromisefy(20),
timerPromisefy(30)
]).then(function (values) {
console.log(values); // 10
});
复制代码
// 库中实现
Promise.race = function(arr) {
return new Promise((resolve, reject) => {
arr.forEach((item, index) => {
item.then(resolve, reject);
});
});}
复制代码
5.Promise的finally
Promise.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};复制代码
5、prmoise 代码库实现
/** * Promise 实现 遵循promise/A+规范 * Promise/A+规范译文: * https://promisesaplus.com/ */
// 判断x是不是promise 根据规范
function resolvePromise(promise2, x, resolve, reject) {
// 1、如果promise2 和 x 指向相同的值, 使用 TypeError做为原因将promise拒绝。 (就会导致循环引用报错)
if (promise2 === x) {
return reject(new TypeError('循环引用'));
}
// 避免多次调用
let isUsed = false;
// 2、如果x是一个promise对象 (该判断和下面 判断是不是thenable(thenable 是一个包含了then方法的对象或函数)对象重复 所以可有可无)
/** * 如果x是pending状态,promise必须保持pending走到x resolved或rejected.
如果x是resolved状态,将x的值用于resolve promise. 如果x是rejected状态, 将x的原因用于reject promise..
*/
// if (x instanceof Promise) { // 获得它的终值 继续resolve
// if (x.status === 'pending') { // 如果为等待态需等待直至 x 被执行或拒绝 并解析y值 递归
// x.then(y => {
// resolvePromise(promise2, y, resolve, reject);
// }, reason => {
// reject(reason);
// });
// } else {
// 如果 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promise
// x.then(resolve, reject);
// }
//
// 3、如果 x 为对象或者函数
//
/**
// * 1.将 then 赋为 x.then.
// 2.如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
// 3.如果 then 是一个函数, 以x为this调用then函数, 且第一个参数是resolve,第二个参数是reject,且:
// 3-1.当 resolve 被以 y为参数调用, 执行 [[Resolve]](promise, y).
// 3-2.当 reject 被以 reason 为参数调用, 则以reason为原因将promise拒绝。
// 3-3.如果 resolvee 和 reject 都被调用了,或者被调用了多次,则只第一次有效,后面的忽略。
// 3-4.如果在调用then时抛出了异常,则:
// 如果 resolve 或 reject 已经被调用了,则忽略它。isUsed = true;
// 否则, 以e为reason将 promise 拒绝。
// 4.如果 then不是一个函数,则 以x为值resolve promise。
//
*/
// } else
if (x !== null && ((typeof x === 'object') || (typeof x === 'function'))) {
try { // 是否是thenable对象(具有then方法的对象/函数) 如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => { // 如果y是promise就继续递归解析promise
if (isUsed) return;
isUsed = true;
resolvePromise(promise2, y, resolve, reject);
}, reason => { // 只要失败了就失败了 不用再递归解析是都是promise
if (isUsed) return;
isUsed = true;
reject(reason);
})
} else { // 说明是一个函数,则 以x为值resolve promise。
resolve(x);
}
} catch (e) {
if (isUsed) return;
isUsed = true;
reject(e);
}
} else { //4、如果 x 不是对象也不是函数,则以x为值 resolve promise。例如 x = 123 或 x ='成功'
resolve(x);
}}
// Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。
// Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
class Promise { // Promise 是一个类, new Promise 返回一个 promise对象 接收一个ex执行函数作为参数, ex有两个函数类型形参resolve reject
/**
* var promise = new Promise(function(resolve, reject) { //会立即执行 // 异步处理
// 处理结束后、调用resolve 或 reject
//当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
//在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
setTimeout(function(){
resolve("成功!"); //代码正常执行!
}, 250);
});
promise.then(function(successMessage){
//successMessage的值是上面调用resolve(...)方法传入的值.
//successMessage参数不一定非要是字符串类型,这里只是举个例子
console.log("Yay! " + successMessage);
});
*/
constructor(ex) { //
this.status = 'pending'; // 初始状态 (表示 未开始)
this.resolveVal = undefined; // resolved状态时(表示成功) 返回的信息
this.rejectVal = undefined; // rejected状态时(表示失败) 返回的信息
this.onResolveCallBackFns = []; // 存储resolved状态对应的onResolved函数 (因为可以链式调用可以多个then方法)
this.onRejectCallBackFns = []; // 存储rejected状态对应的onRejected函数
/**
* 一个Promise必须处在其中之一的状态:pending, fulfilled 或 rejected.
* * 如果是pending状态,则promise:
可以转换到resolved或rejected状态。
如果是resolved状态,则promise:
不能转换成任何其它状态。
必须有一个值,且这个值不能被改变。
如果是rejected状态,则promise可以:
不能转换成任何其它状态。
必须有一个原因,且这个值不能被改变。
”值不能被改变”指的是其identity不能被改变,而不是指其成员内容不能被改变。
*/
let resolve = (data) => { // data 成功态时接收的终值
if (this.status === 'pending') {
// 只能由pedning状态 => resolved状态 避免调用多次resolve reject)
this.status = 'resolved';
this.resolveVal = data;
this.onResolveCallBackFns.forEach(cb => cb());
}
}
let reject = (err) => {
if (this.status === 'pending') {
// 只能由pedning状态 => rejected状态 避免调用多次resolve reject)
this.status = 'rejected';
this.rejectVal = err;
this.onRejectCallBackFns.forEach(cb => cb());
}
}
// 捕获在ex执行器中抛出的异常
// new Promise((resolve, reject) => {
// throw new Error('error in ex')
// })
try {
ex(resolve, reject);
} catch (e) {
reject(e);
}
}
// 按照prmoise a+ 规范 then方法接受两个参数 then方法是异步执行的 必须返回一个promise then(resolve, reject) { // then 方法 resolve,reject 都是可选参数 保证参数后续能够继续执行 //
1.1、 如果resolve不是一个函数,则忽略之。 如果reject不是一个函数,则忽略之。
resolve = typeof resolve == 'function' ? resolve : y => y;
reject = typeof reject == 'function' ? reject : err => { throw err };
let promise2; // then必须返回一个promise
if (this.status === 'pending') { // 等待态
// 当异步调用resolve/rejected时 将resolve/reject收集暂存到集合中
promise2 = new Promise((res, rej) => {
this.onResolveCallBackFns.push(() => {
setTimeout(() => {
try {
// resolvePromise可以解析x和promise2之间的关系 /** 如果resolve 或 reject 返回了值x, 则执行Promise 解析流程[[Resolve]](promise2, x). // 如果resolve 或 reject 抛出了异常e, 则promise2应当以e为rejectVal被拒绝。 // 如果 resolve 不是一个函数且promise1已经resolved,则promise2必须以promise1的值resolved. // 如果 reject 不是一个函数且promise1已经rejected, 则promise2必须以相同的rejectVal被拒绝. */ let x = resolve(this.resolveVal); resolvePromise(promise2, x, res, rej); } catch (e) { rej(e); } }, 0); });
this.onRejectCallBackFns.push(() => {
setTimeout(() => {
try {
let x = reject(this.rejectVal);
resolvePromise(promise2, x, res, rej);
} catch (e) {
rej(e);
}
}, 0);
});
});
}
if (this.status == 'resolved') {
// 它必须在promise resolved后调用, 且promise的value为其第一个参数。
promise2 = new Promise((res, rej) => {
// 用setTimeout方法原因 :
// 1、方法是异步的
// 2、 对于一个promise,它的then方法可以调用多次.(当在其他程序中多次调用同一个promise的then时 由于之前状态已经为resolved/rejected状态,则会走的下面逻辑),
// 所以要确保为resolved/rejected状态后 也要异步执行resolve/reject 保持统一
setTimeout(() => {
try {
let x = resolve(this.resolveVal);
resolvePromise(promise2, x, res, rej); //
// resolvePromise可以解析x和promise2之间的关系
} catch (e) {
rej(e);
}
})
})
}
if (this.status == 'rejected') { // 必须在promise rejected后调用, 且promise的rejectVal为其第一个参数
promise2 = new Promise((res, rej) => { // 方法是异步的 所以用setTimeout方法
setTimeout(() => {
try {
let x = reject(this.rejectVal);
resolvePromise(promise2, x, res, rej); // resolvePromise可以解析x和promise2之间的关系
} catch (e) {
rej(e);
}
});
})
}
return promise2; // 调用then后返回一个新的promise }
// catch接收的参数 只用错误 catch就是then的没有成功的简写
catch(err) {
return this.then(null, err);
}}
/** * Promise.all Promise进行并行处理
* 参数: arr对象组成的数组作为参数
* 返回值: 返回一个Promise实例
* 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。
*/
Promise.all = function (arr) {
return new Promise((resolve, reject) => {
let num = 0, innerArr = [];
function done(index, data) {
innerArr[index] = data;
num++;
if (num === arr.length) {
resolve(innerArr);
}
}
for (let i = 0; i < arr.length; i++) {
arr[i].then((res) => {
done(i, res);
}, reject); // 有一个失败 就返回
} })}
/** * Promise.race
* 参数: 接收 promise对象组成的数组作为参数
* 返回值: 返回一个Promise实例
* 只要有一个promise对象进入 resolved 或者 rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快)
*/
Promise.race = function (arr) {
return new Promise((resolve, reject) => {
arr.forEach((item, index) => {
item.then(resolve, reject);
});
});}
// Promise.reject 返回一个rejected状态的promise对象
Promise.resolve = function (val) {
return new Promise((resolve, reject) => resolve(val))}
// .Promise.resolve 返回一个fulfilled状态的promise对象
Promise.reject = function (val) {
return new Promise((resolve, reject) => reject(val));}
Promise.deferred = Promise.defer = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;}
module.exports = Promise;复制代码
6、测试
let p = new Promise((resolve, reject) => {
reject('err');})
p.then().then().catch(r => {
console.log(r);}).
then(data => {
console.log('data', data);})
执行结果:
errdata undefined
复制代码
let fs = require('fs');
function read() {
// 好处就是解决嵌套问题
// 坏处错误处理不方便了
let defer = Promise.defer();
fs.readFile('./1.txt', 'utf8', (err, data) => {
if (err) defer.reject(err);
defer.resolve(data) });
return defer.promise;}
read().then(data => {
console.log(data);});
执行结果
我是1.txt内容复制代码