3、promise的链式调用
-
在上面我们已经对异步处理进行优化,但是我们知道promise主要是为了解决回调地狱的问题,那么就涉及到链式调用,很明显,我们上面的代码还不满足链式调用;如果需要满足链式调用,那么then方法中应当返回一个promise
-
首先,我们先来看一个promise链式调用的简单demo,其中name.txt存放的是"./age.txt",age.txt存放的是"age is 9;"
let fs = require('fs');
function readFile(url) {
return new Promise((resolve,reject) => {
fs.readFile(url,'utf8',(err, data) => {
if (err){
reject(err);
}
resolve(data);
})
})
}
readFile('./name.txt').then((data) => {
return readFile(data);
}).then((data) => {
console.log('success--------'+data)
}).catch(err => {
console.log('>>>>>>>>>>>>>>>>'+err);
throw new Error('error')
});
// 输出为:success--------age is 9;
- 其次:我们对官方的promise中的then的返回进行分析:
promise2 = promise1.then(onFulfilled, onRejected);
- then的返回值必须是一个promise
- 如果onFulfilled 或 onRejected返回一个普通值,则运行下面的promise解决过程[[Resolve]](promise2, x)
- 如果onFulfilled 或 onRejected抛出异常e,那么会走向promise2的reject方法,并传入参数e
- 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
- 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
- 了解了then的返回值,但是如果要完善自身的promise,我们还应当对Promise的解决过程有一定的了解,即解决onFulfilled 或 onRejected返回值x与promise2的关系:
- 如果x与promise2指的是同一个对象,那么以TypeError为原因执行promise2的reject
- 如果x是一个promise,那么接受它的状态
- 如果x处于pending状态,promise2必须保持pending状态直到x执行或被拒绝
- 如果x处于执行状态,那么以相同的值执行promise2
- 如果x处于拒绝状态,那么以相同的原因拒绝promise2
- 如果x是一个对象或者函数:
- 把x.then赋值给then;
- 如果获取x.then时抛出异常e,那么以e为reason拒绝promise2
- 如果此时获取的then是一个函数,那么将函数中的this指向x,其第一个参数为resolvePromise,第二个参数为rejectPromise
- 如果resolvePromise被调用,并有参数y,那么则执行[[Resolve]](promise, y)
- 如果rejectPromise被调用,并有reason-r,那么则以r为参数拒绝promise
- 如果resolvePromise 和 rejectPromise 均被调用,或同一个参数被调用多次,那么则以第一次调用为优先,之后的调用则被忽略
- 如果调用then方法时抛出了异常e:
- 如果resolvePromise 或者 rejectPromise已经被调用,那么忽略它
- 否则以e为拒因来决绝promise
- 如果then不是一个函数,那么执行promise,其参数为x
- 如果x不是一个对象或者方法,那么执行promise,其参数为x
代码如下:
class Promise {
constructor(fn){
this.data = undefined; // 回调成功的data
this.error = undefined; // 失败回调的error
this.status = "pending";
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
// 下面在fn中传入resolve和reject方法时,需要进行this绑定,否则在resolve和reject方法中获取到的this为undefined
fn(this.resolve.bind(this), this.reject.bind(this));
}
resolve(data){
if (this.status === "pending") {
// 重置状态为fulfilled,并置this.data为获取的数据
this.status = "fulfilled";
this.data = data;
this.onResolvedCallbacks.forEach(fn => {
fn();
})
}
}
reject(error){
// 重置状态为rejected,并置this.error为错误信息
if (this.status === "pending") {
this.status = "rejected";
this.error = error;
this.onRejectedCallbacks.forEach(fn => {
fn();
})
}
}
then(onfulfilled,onrejected){
// 执行then方法后,应返回一个promise,以便进行链式调用
let promise2 = new Promise((resolve, reject) => {
// 当状态为fulfilled时,执行成功的回调
if(this.status === "fulfilled"){
// 使用setTimeout是为了防止,在里面使用promise2的时候,promise2还没有值
setTimeout(() => {
// 使用try,catch是为了,如果onFulfilled 或 onRejected抛出异常e,那么会走向promise2的reject方法,并传入参数e
try{
let x = onfulfilled(this.data); // 获取onfulfilled的返回值
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
}, 0);
}
// 当状态为rejected时,执行成功的回调
if (this.status === "rejected"){
setTimeout(() => {
try{
let x = onrejected(this.error); // 获取onrejected的返回值
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
}, 0);
}
// 当状态为pending时,那么将onfulfilled与onrejected方法进行存储,在执行resolve或者reject时进行执行
if(this.status === "pending"){
// 这里push onfulfilled 与 onrejected 方法时,不直接push,而是push一个函数,在函数中执行onfulfilled 与 onrejected是为了获取resolve或者reject函数中传递的data或者error
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try{
let x = onfulfilled(this.data);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try{
let x = onrejected(this.error);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
}, 0);
});
}
});
return promise2;
}
}
// Promise的解决过程
function resolvePromise(promise2, x, resolve, reject){
// 这里的判断是为了防止then中的返回值是promise本身,否则会造成死循环,因此当返回的是promise本身,那么抛出类型错误
if (promise2 === x){
return reject(new TypeError("Chaining cycle detected for promise"));
}
let called; // 用于判断是否被多次调用
if((x !== null && typeof x === "object") || typeof x === "function"){
try{
let then = x.then; // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
if (typeof then === "function"){
// 如果 then 是函数,将 x 作为函数的作用域 this 调用之;传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
then.call(x, y => {
if (called) return ;
called = true;
// 继续对y的返回进行判断
resolvePromise(promise2, y, resolve, reject)
}, r => {
if (called) return ;
called = true;
reject(r);
})
}else {
// then的返回值是一个普通对象,将值传递给resolve
resolve(x);
}
}catch(e){
if (called) return ;
called = true;
reject(e);
}
}else {
// 如果then的返回值为普通值,那么将这个值传递给下一个then的resolve函数中
resolve(x);
}
}
module.exports = Promise;
4、promise测试
- 在对我们自己写的promise进行测试之前,还有一个问题的存在。上面我们提到过,如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值;如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因;
- 我们首先看下面的例子:
let p = new Promise((resolve, reject) => {
resolve("成功执行啦~~")
});
p.then().then().then(data => {
console.log(data);
},err => {
console.log("err---------------"+err);
});
- 运行上面例子可知,在前两个then中,是没有onFulfilled这个函数的,此时,会一直向下执行,并且会返回相同的值;但如果使用我们写的promise时,则会报如下错误:
err---------------TypeError: onrejected is not a function
- 这是由于在我们自己写promise中,onFulfilled与onRejected都不为函数,此时,如果运行的话,这两个参数由于都不是函数,但在then方法中,当我们其进行执行时,便会报错:onrejected is not a function,那么就会走到对应的catch中去,也就会执行reject,因此也就出现了上面的报错
- 如果要解决这个问题,需要对onFulfilled与onRejected不是函数时进行处理,并令其成为一个函数,在then方法开始执行时添加如下代码:
onfulfilled = typeof onfulfilled === "function" ? onfulfilled : data => data;
// 注:这里不能返回一个err,如果返回err的话,那么会进入下一个promise的resolve,而非reject
onrejected = typeof onrejected === "function" ? onrejected : err => {throw err};
- 最后我们还有一个小功能没有实现,就是promise的catch方法,catch 其实是 then(undefined, () => {}) 的语法糖,因此这个方法比较简单,如下所示:
catch(onrejected){
this.then(undefined,onrejected);
}
- 以上我们基本完成了promise的功能,下面通过promises-tests来对我们写的promise进行测试。但在测试之前官方文档有如下提到:
In order to test your promise library, you must expose a very minimal adapter interface. These are written as Node.js modules with a few well-known exports:
- resolved(value): creates a promise that is resolved with value.
- rejected(reason): creates a promise that is already rejected with reason.
- deferred(): creates an object consisting of { promise, resolve, reject }:
- promise is a promise that is currently in the pending state.
- resolve(value) resolves the promise with value.
- reject(reason) moves the promise from the pending state to the rejected state, with rejection reason reason.
- 上面的意思是说:为了测试我们自己的promise库,需要暴露一些小的适配接口。这些是作为Node.js模块所写的,其中有一些众所周知的导出;其中resolved 和 rejected都为可选的,如果他们不存在,那么会在测试运行时使用deferred来进行自动创建,下面我们根据上面描述实现deferred
Promise.deferred = function(){
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
-
最后一步,我们就要对promise进行测试啦~
- promises-aplus-tests安装:npm install -g promises-aplus-tests
- 进入promise.js所在的目录运行:promises-aplus-tests .\promise.js
- 测试成功~
-
完整代码如下:
class Promise {
constructor(excutor){
this.data = undefined; // 回调成功的data
this.error = undefined; // 失败回调的error
this.status = "pending";
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
// 下面在fn中传入resolve和reject方法时,需要进行this绑定,否则在resolve和reject方法中获取到的this为undefined
try{ // 为了防止在执行promise时抛出异常
excutor(this.resolve.bind(this), this.reject.bind(this));
}catch(e){
this.reject.call(this, e);
}
}
resolve(data){
if (this.status === "pending") {
// 重置状态为fulfilled,并置this.data为获取的数据
this.status = "fulfilled";
this.data = data;
this.onResolvedCallbacks.forEach(fn => {
fn();
})
}
}
reject(error){
// 重置状态为rejected,并置this.error为错误信息
if (this.status === "pending") {
this.status = "rejected";
this.error = error;
this.onRejectedCallbacks.forEach(fn => {
fn();
})
}
}
then(onfulfilled,onrejected){
onfulfilled = typeof onfulfilled === "function" ? onfulfilled : data => data;
// 注:这里不能返回一个err,如果返回err的话,那么会进入下一个promise的resolve,而非reject
onrejected = typeof onrejected === "function" ? onrejected : err => {throw err};
// 执行then方法后,应返回一个promise,以便进行链式调用
let promise2 = new Promise((resolve, reject) => {
// 当状态为fulfilled时,执行成功的回调
if(this.status === "fulfilled"){
// 使用setTimeout是为了防止,在里面使用promise2的时候,promise2还没有值
setTimeout(() => {
// 使用try,catch是为了,如果onFulfilled 或 onRejected抛出异常e,那么会走向promise2的reject方法,并传入参数e
try{
let x = onfulfilled(this.data); // 获取onfulfilled的返回值
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e)
}
}, 0);
}
// 当状态为rejected时,执行成功的回调
if (this.status === "rejected"){
setTimeout(() => {
try{
let x = onrejected(this.error); // 获取onrejected的返回值
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
}, 0);
}
// 当状态为pending时,那么将onfulfilled与onrejected方法进行存储,在执行resolve或者reject时进行执行
if(this.status === "pending"){
// 这里push onfulfilled 与 onrejected 方法时,不直接push,而是push一个函数,在函数中执行onfulfilled 与 onrejected是为了获取resolve或者reject函数中传递的data或者error
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try{
let x = onfulfilled(this.data);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try{
let x = onrejected(this.error);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}
}, 0);
});
}
});
return promise2;
}
catch(onrejected){
this.then(undefined,onrejected);
}
}
// Promise的解决过程
function resolvePromise(promise2, x, resolve, reject){
// 这里的判断是为了防止then中的返回值是promise本身,否则会造成死循环,因此当返回的是promise本身,那么抛出类型错误
if (promise2 === x){
return reject(new TypeError("Chaining cycle detected for promise"));
}
let called; // 用于判断是否被多次调用
if((x !== null && typeof x === "object") || typeof x === "function"){
try{
let then = x.then; // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
if (typeof then === "function"){
// 如果 then 是函数,将 x 作为函数的作用域 this 调用之;传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
then.call(x, y => {
if (called) return ;
called = true;
// 继续对y的返回进行判断
resolvePromise(promise2, y, resolve, reject)
}, r => {
if (called) return ;
called = true;
reject(r);
})
}else {
// then的返回值是一个普通对象,将值传递给resolve
resolve(x);
}
}catch(e){
if (called) return ;
called = true;
reject(e);
}
}else {
// 如果then的返回值为普通值,那么将这个值传递给下一个then的resolve函数中
resolve(x);
}
}
Promise.deferred = function(){
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
module.exports = Promise;