深入了解promise机制,手动撸一个promise

promise 机制

关于前端架构只是体系中的知识剩下的部分一直没有不全,不是不想写,实在是不敢轻易下笔,如果深度不够,我想关于es6的只是点我们都可以参考 阮一峰的es6-promise教程, 如果需要一定的深度,不仅仅需要查找一些资料,更重要的是还要吸收而后进一步整理。所以跟新完善的进展有点慢,但是,要么不做,要么就做好。我相信当您阅读完本文,关于 promise 相信您会有一个全新的认识。

温馨提示:
本文适合对es6是相对熟悉的看官阅读,如果您对es6还不是那么了解,建议先看完 阮一峰老师es6-promise教程 后再阅读本文

为了让您读起来不枯燥,咱们一边看题,一边思考,一边完善

  • 先看最基础的使用
new Promise((resolve,reject)=>{
  // resolve() 
  // reject();
})

分析: 通过上面代码不难看书,Promise 就是一个构造函数,并且接受两个方法作为参数
我们的雏形代码如下:

Promise1 function(excutor) {
  function resolve(value){}
  function reject(value) {}
  exctuor(resolve, reject)
}
  • 官方文档告诉我们说(1)对象的状态不受外界影响。(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果
var p1 = new Promise(function(resolve, reject) {
    resolve(1)
    throw Error('sync error')
})
.then(res => {
   console.log(11111,res) // 11111 1
}, (error) => {console.log(error)})
.catch(err => {
   console.log(err)
})
// 控制台输出 11111 1

分析:结合官方的说法和实际代码的运行结果,我们猜测,状态不仅仅是内部私有的,而且改变之后不可逆,根据 es6 中的说法三种状态分别为 pending(进行中)、fulfilled(已成功)和rejected(已失败)

进一步改善我们的代码

Promise1 function(excutor) {
  let self = this; // 保持良好的习惯,避免一些不要的坑
  self.status = 'pending'; // 初始状态为pending
  self.value = null; // 返回值默认为null
  self.reason = null; // 错误信息默认为null
  function resolve(value){
  	// 不可逆操作
  	if (self.status === 'pending') {
  		self.status = 'fulfilled';
  		self.value = value;
  	}
  }
  function reject(reason) {
  	// 不可逆操作
  	if (self.status === 'pending') {
  		self.status = 'rejected'
  		self.reason = reason;
  	}
  }
  // 为了避免 resolve 和 reject 方法本身出错,我们容错一下
  try {
    exctuor(resolve, reject)
  }catch(err) {
  	reject(err) // 通过reject来记录错误信息
  }
}

Promise1.prototype.then = function (onFulfilled, onRejected) {
  let self = this;
  // then 方法后面的成功的回调
  if (self.status === 'fulfilled') {
  	try{
  		onFulfilled(self.value) // 返回值
  	}catch(error) {
  		self.reject(error)
  	}
  }
  // then 方法后面的失败的回调
  if (self.status === 'rejected') {
  	try{
  		onRejected(self.value) // 返回值
  	}catch(error) {
  		self.reject(error)
  	}
  }
  if (self.status === 'pending') {
  	
  }
}
  • Promise 是异步编程的一种解决方案
    看代码
new Promise(function(resolve, reject) {
  //使用定时器来模拟异步
  setTimeout(() => {
    resolve(100);
  }, 1000);
}).then(function(value) {
  console.log(11111111, value);
});
// 一秒之后,控制台仍然答应出了 11111111 100

但是根据我们上面的实现1000之后,整个函数体内的上下就消失了,那么这个在设计上显然不合理,怎么办呢?那我们就先保存一下,等到整个运行结束再来执行,代码进一步完善如下:

Promise1 function(excutor) {
  let self = this; // 保持良好的习惯,避免一些不要的坑
  self.status = 'pending'; // 初始状态为pending
  self.value = null; // 返回值默认为null
  self.reason = null; // 错误信息默认为null
  self.onFulfilledCallbacks = []; // 成功回调的队列
  self.onRejectedCallbacks = []; // 失败回调的队列
  function resolve(value){
  	// 不可逆操作
  	if (self.status === 'pending') {
  		self.status = 'fulfilled';
  		self.value = value;
  	}
  }
  function reject(reason) {
  	// 不可逆操作
  	if (self.status === 'pending') {
  		self.status = 'rejected'
  		self.reason = reason;
  	}
  }
  // 为了避免 resolve 和 reject 方法本身出错,我们容错一下
  try {
    exctuor(resolve, reject)
  }catch(err) {
  	reject(err) // 通过reject来记录错误信息
  }
}

Promise1.prototype.then = function (onFulfilled, onRejected) {
  let self = this;
  // then 方法后面的成功的回调
  if (self.status === 'fulfilled') {
  	try{
  		onFulfilled(self.value) // 返回值
  	}catch(error) {
  		self.reject(error)
  	}
  }
  // then 方法后面的失败的回调
  if (self.status === 'rejected') {
  	try{
  		onRejected(self.reason) // 返回值
  	}catch(error) {
  		self.reject(error)
  	}
  }
  // 当状态处于pending状态时,像队列中添加各自的回调,然后在同一执行
  if (self.status === 'pending') {
  	self.onFulfilledCallbacks.push(() => {
  	 try {
  	 	onFulfilled(self.value)
  	 }catch(error){
  	 	self.reject(err);
  	 }
  	})
  	self.onFulfilledCallbacks.push(() => {
  	 try {
  	 	onRejected(self.reason)
  	 }catch(error){
  	 	self.reject(err);
  	 }
  	})
  }
}
  • 到目前为止,看起来基本的promise结构已经出来了,但是promise是可以链式调用的,所谓的链式调用的本质上就是每一个方法都返回一个新的promise 实例,这个就相对简单了,在我们所有的参数方法外面包一层 new Promise() 就好了
    代码如下:
Promise1.prototype.then = function(onFulfilled, onRejected) {
  let self = this;
  if (self.status === "fulfilled") {
    // 返回新的Promise
    return new Promise1((resolve, reject) => {
      try {
      	// 如果不指定错误的回调函数,会再次抛出错误,
        let x = onFulfilled(self.value);
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "rejected") {
    return new Promise1((resolve, reject) => {
      try {
        let x = onRejected(self.reason); 
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "pending") {
    return new Promise1((resolve, reject) => {
      self.onFulfilledCallbacks.push(() => {
        let x = onFulfilled(self.value);
      });
      self.onRejectedCallbacks.push(() => {
        let x = onRejected(self.reason);
      });
    });
  }
  • 听所then的回调函数还可以继续返回promise
new Promise(function(resolve, reject) {
  // TODO
  resolve(11111);
}).then(res => {
  return new Promise(function(resolve, reject) {
    resolve(res);
  }).then(res => {
    console.log(2222, res);
  });
});
// 控制台输出 2222 11111

这样的话,我们上面的代码还不够,的进一步判断返回的是否是一个promise实例
代码如下:

Promise1.prototype.then = function(onFulfilled, onRejected) {
  let self = this;
  if (self.status === "fulfilled") {
    // 返回新的Promise
    return new Promise1((resolve, reject) => {
      try {
      	// 如果不指定错误的回调函数,会再次抛出错误,
        let x = onFulfilled(self.value);
        // 针对返回做判断,如果是一个Promise对象,就继续 .then 保持整个函数的链式结构,否则就调用 resolve 方法
        x instanceof Promise1 && x.then(resolve, reject) : resolve(x)
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "rejected") {
    return new Promise1((resolve, reject) => {
      try {
        let x = onRejected(self.reason); 
         x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "pending") {
    return new Promise1((resolve, reject) => {
      self.onFulfilledCallbacks.push(() => {
        let x = onFulfilled(self.value);
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      });
      self.onRejectedCallbacks.push(() => {
        let x = onRejected(self.reason);
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      });
    });
  }

到目前为止,整个 Promise 的代码 如下:

function Promise1(excutor) {
  let self = this;
  self.status = "pending";
  self.value = null;
  self.reason = null;
  self.onFulfilledCallbacks = []; // 成功回调的队列
  self.onRejectedCallbacks = []; // 失败回调的队列

  // 什么时候调用,什么时候改变状态,且状态不可逆
  function resolve(value) {
    if (self.status === "pending") {
      self.value = value;
      self.status = "fulfilled";
      // 以下方法会在先调用then回调之后,在resolve的时候执行
      self.onFulfilledCallbacks.length && self.onFulfilledCallbacks.forEach(item => item());
    }
  }

  function reject(reason) {
    if (self.status === "pending") {
      self.reason = reason;
      self.status = "rejected";
      self.onFulfilledCallbacks.length && self.onRejectedCallbacks.forEach(item => item());
    }
  }

  // 这里可以捕获一些不必要的错误,eg: excute 不是一个 funciton,或者 excute 内部出错
  try {
    excutor(resolve, reject); // 构造函数传进来的函数
  } catch (err) {
    reject(err);
  }
}

Promise1.prototype.then = function(onFulfilled, onRejected) {
  let self = this;

  if (self.status === "fulfilled") {
    // 返回新的Promise
    return new Promise1((resolve, reject) => {
      try {
        let x = onFulfilled(self.value);
        // then 的回调也有可以返回一个Promise
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "rejected") {
    return new Promise1((resolve, reject) => {
      try {
        let x = onRejected(self.reason); // 如果不指定错误的回调函数,会再次抛出错误,
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "pending") {
    return new Promise1((resolve, reject) => {
      self.onFulfilledCallbacks.push(() => {
        let x = onFulfilled(self.value);
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      });
      self.onRejectedCallbacks.push(() => {
        let x = onRejected(self.reason);
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      });
    });
  }
};
  • 此时此刻官方又告诉我们说 Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数

这就帅歪歪了啊,啥都不用谢直接复制就好了

promise 添加 catch 方法:

Promise1.prototype.catch = function(errorCallBack) {
  return this.then(null, errorCallBack);
};

另外的两个方法也直接附上

Promise1.reject = function(reason) {
  return new Promise1((resolve, reject) => {
    reject(reason);
  });
};

Promise1.resolve = function(value) {
  return new Promise1((resolve, reject) => {
    resolve(value);
  });
};
Promise.prototype.finally = function(callback) {
  let self = this.constructor;
  return self.then(
    value => self.resolve(callback()).then(() => value),
    reason =>
      self.resolve(callback()).then(() => {
        throw reason;
      })
  );
};

到此为止整个 手动 撸 Promise 的过程全部结束。当然相信你对 promise 有了更加深刻的理解

接下来上一组 promise 相关的面试题来热热身,促进一下消化
思考一下一面三组代码的输出结果
promise 捕获错误:
🌰1:

new Promise(function(resolve, reject) {
    throw Error('sync error')
})
.then(res => {
   console.log(res)
})
.catch(err => {
    console.log(err)
})

🌰2:

new Promise(function(resolve, reject) {
    setTimeout(() => {
        throw Error('async error')   
    })
})
.then(res => {
    console.log(res)
})
.catch(err => {
    console.log(err)
})

🌰3:

 new Promise(function(resolve, reject) {
    resolve()
})
.then(res => {
   throw Error('sync error') 
})

正确答案为:

  • 第一组错误被catch 捕获,控制台输出错误信息 Error: sync error
  • 第二组的定时器抛出错误的时候,catch 已失效,无法正确捕获错误,控制台直接报错
  • 第三组抛出错误信息在promise内部,但是由于没有被catch捕获,直接抛出到外部,控制台直接报错

将上面的代码稍作修改,再来一次
promise 状态一旦发生改变不可逆转:
🌰1:

new Promise(function(resolve, reject) {
    resolve(1)
    throw Error('sync error')
})
.then(res => {
        console.log(res)
})
.catch(err => {
        console.log(err)
})

🌰2:

new Promise(function(resolve, reject) {
    reject(2)
    resolve(1)
})
.then(res => {
        console.log(res)
})
.catch(err => {
        console.log(err)
})

🌰3:

new Promise(function(resolve, reject) {
    resolve(1)
})
.then(res => {
     throw Error('sync error')
     console.log(res)
})
.catch(err => {
        console.log(err)
})

正确答案为:

  • 第一组,控制台输出1, 一旦状态发生改变不可逆转,后面的代码得不到执行
  • 第二组,控制台输出2, 原因同上
  • 第三组,then 内部抛出的错误被外部捕获,控制台打印出错误信息

咱们继续,还不够爽
promise 中的回调是异步的:

new Promise(function(resolve, reject) {
    resolve()
    setTimeout(() => {
        console.log(1)
    })
    console.log(2)
})
.then(res => {
        console.log(3)
})
console.log(4)

答案: 1432
new promise 是属于构造函数,会立即执行,控制台先输出3
setTimeout 延时,在空闲是才会执行 最后输出 1
then 方法也是会在整个promise 的回调结束之后执行,而且是一个异步的,所以会处于等待状态,代码继续向下执行, 控制台打印 4 ,而后再返回来执行then中的回调,输出 3

promise 返回一个新的promise(链式调用):

new Promise(function(resolve, reject) {
    reject(1)
})
.then(res => {
  console.log(res)
  return 2
},err => {
  console.log(err) // 输出 1
  return 3
 }
)
.catch(err => {
  // 有err回调,这一步跳过
  console.log(err)
  return 4
})
.finally(res => {
   // 该函数内部的代码执行和状态以及返回值无关
   console.log(res) // 直接返回 undefined 参见 Promise.prototype.finally
   return 5
})
.then(
    res => console.log(res), // err 回调中返回了新的值为3,所以执行成功的回调
    err => console.log(err)
)

输出结果:

  • 1
  • undefined
  • 3
    原因已经进行了说明,就不在解析

希望对各位看官有所帮助

其它前端性能优化:

前端技术架构体系(没有链接的后续跟进):

其它相关

欢迎各位看官的批评和指正,共同学习和成长
希望该文章对您有帮助,你的 支持和鼓励会是我持续的动力

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章