0
点赞
收藏
分享

微信扫一扫

Promise从入门到手写 | [Promise系列一]

Promise从入门到手写 | [Promise系列一]_JavaScript


作者:寒草

写作背景

其实我最开始想写的并不是​​Promise​​​这篇文章,而是想总结一些​​axios​​​相关的知识,但是写文章必须有所依据,想要去讲好​​axios​​​,那么对他的前置知识一定要了解,所以我想不妨把这做成一个系列,于是就有了从​​Promise​​​,​​Ajax​​​开始,再去讲​​Axios​​​的打算。其实相信大家做前端开发的一定对​​Promise​​​不陌生了,所以大家伙也不妨跟着我对​​Promise​​这个基础知识进行回顾。 本文将包括:

  • ​Promise​​介绍
  • ​Promise​​特点
  • ​Promise​​使用
  • 根据前文的使用方式和特性,手写​​Promise​

话不多说,我们直接开始!

Promise介绍

Promise起源与用途

  • ​Promise​​最早在社区提出和实现,在ES6写入了语言标准。


tip: ES6即ECMA-262第六版,这一版包含了这个规范有史以来最重要的一批增强特性,Javascript是ECMA-262规范的实现


  • ​Promise​​是异步编程的解决方案,比传统的回调函数解决方式更加合理更加强大更加优雅。
  • 语法上:使用Promise构造函数对异步操作进行封装以生成Promise实例
  • 功能上:promise对象用来封装一个异步操作并提供统一的API,使得各种异步操作都可以用同样的方式进行处理

常见的异步编程场景

  • fs文件操作
require('fs').readFile('./index.html',(err,data)=>{
// 回调函数
})
  • Ajax操作
$.get('/api/getUser',(data)=>{
//handleData();
})
  • 定时器
setTimeout(() => {
console.log('timeout');
},1000);

为什么使用Promise

  • 支持链式调用,将异步操作以同步操作的流程表达出来,可以解决回调地狱问题

什么是回调地狱?

回调函数嵌套调用,外部回调函数异步执行结果是嵌套的回调的执行条件

// 回调地狱典型场景
asyncFunc1(opt,(...args1) => {
asyncFunc2(opt,(...args2) => {
asyncFunc3(opt,(...args3) => {
asyncFunc4(opt,(...args4) => {
//TODO: some opt
})
})
})
})

回调地狱的缺点:

不便于阅读,不便于异常处理,不利于身心愉快

  • 指定回调函数的方式更加灵活

传统方式: 必须在启动异步任务之前指定

Promise: 可以随时监听异步任务的状态,随时指定回调函数,一个或者多个。

  • Promise提供统一的api,使得控制异步操作更加容易。

提供了哪些api在后续的使用中会详细阐述

Promise特点

Promise的特性

  • 对象状态不受外界影响

​Promise​​​对象代表一个异步操作,有三种状态:​​pending​​​进行中,​​fulfilled​​​成功,​​rejected​​​失败 只有异步操作结束才能改变状态,其他任何操作都不能改变。状态存储在Promise对象的​​​[[PromiseState]]​​属性中。

// Promise的两个属性:
let promiseA = new Promise((resolve,reject)=>{resolve();})
// 状态对应promiseA的[[PromiseState]]字段。
let promiseB = new Promise((resolve,reject)=>{resolve(1111);})
// [[PromiseResult]]的值为 1111,这个字段用于存储 resolve(val)或者reject(val)的参数[val]


tips: Promise本意是承诺,也是因为此原因,想一想这是多么浪漫的名字。


  • 一旦状态改变,就不会再发生变化

Promise 对象的状态改变,只有两种可能:从 ​​Pending 变为 Resolved​​​ 和从 ​​Pending 变为 Rejected​​。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

Promise的缺点

  • 一旦新建,立即执行,无法中途取消
  • ​Promise​​内部抛出的错误,无法反映到外部
  • ​pending​​状态时无法知道进展到哪一个阶段

Promise使用

Promise实例创建

const promise = new Promise((resolve, reject) => {
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是​​resolve​​​和​​reject​​。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

  • resolve函数:将Promise对象的状态从“未完成”变为“成功”(即从​​pending​​​ 变为​​fulfilled​​),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
  • reject函数:将Promise对象的状态从“未完成”变为“失败”(即从​​pending​​​ 变为​​rejected​​),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise.prototype.then

Promise实例生成以后,可以用​​then​​方法分别指定​​fulfilled​​状态和​​rejected​​状态的回调函数

最终会返回一个新的​​Promise​​对象


tip: then中的回调函数是可选的,不一定要提供


promise.then(function(value) {
// fulfilled状态的处理
}, function(error) {
// rejected状态的处理
});

Promise.prototype.catch

catch用于处理状态为​​rejected​​的回调函数

最终会返回一个新的​​Promise​​对象

promise.catch((err)=>{
handleReject();
//
})

Promise.resolve

返回成功或者失败的Promise对象

let promiseA = Promise.resolve(1);
// 如果传入的参数为非Promise类型的对象,则返回的结果为成功的Promise对象
let PromiseB = Promise.resolve(new Promise((resolve,reject)=>{
reject('err');
})
// 如果传入的参数为Promise对象,则参数Promise返回的结果就是 Promise.resolve返回的结果
// 比如这时return 一个[[PromiseResult]]的值为err的Promise对象

Promise.reject

返回一个失败的Promise对象

let PromiseA = Promise.reject(new Promise((resolve,reject)=>{
resolve('err');
})
// 无论传入是啥,就返回一个失败的Promise对象,[[PromiseResult]]的值为 Promise.reject的参数

Promise.all

接收的参数是由n个Promise对象的数组。


tips: 返回结果是新的promise,只有所有的Promise对象都成功才成功,只要有一个失败了就直接失败


let promise1 = Promise.resolve(1);
let promise2 = Promise.resolve(2);
let promise3 = Promise.reject(3);
const res = Promise.all([promise1,promise2,promise3]);
//此时输出为 [[PromiseState]]是rejected,[[PromiseResult]]是3的Promise
const res = Promise.all([promise1,promise2]);
//此时输出为 [[PromiseState]]是fulfilled,[[PromiseResult]]是[1,2]的Promise

Promise.race

接收的参数是由n个Promise对象的数组。


tips: 返回结果是新的promise,第一个完成的promise的结果状态就是最终结果的状态。


let promise1 = Promise.resolve(1);
let promise2 = Promise.resolve(2);
let promise3 = Promise.reject(3);
const res = Promise.race([promise1,promise2,promise3]);
//此时输出为 [[PromiseState]]是fulfilled,[[PromiseResult]]是1的Promise

常见问题

如何改变Promise对象的状态

  • resolve() // pending => fulfilled
  • reject() // pending => rejected
  • 抛出错误 throw 'err' // pending => rejected

promise.then() 的返回结果

  • 如果抛出异常,则返回rejected的Promise对象
  • 如果返回的是非promise类型的任意值,则返回状态为resolved的Promise对象
  • 如果返回的是一个新的promise,则该promise的结果会成为新的promise结果

promise为什么可以链式调用

因为then,catch,all,race等等所有的promise的api的返回值是新的promise对象。

所以可以继续打点调用promise的方法,以此种方式将任务串联起来

promise的异常穿透

  • 当使用promise的then进行链式调用时,可以在最后指定失败的回调
  • 前面的任何错误都会在最后传到失败的回调中去处理
let p1 = Promise.resolve(1);
p1.then((value)=>{
console.log(11);
}).then((value)=>{
throw 'err';
}).then((value)=>{
console.log(22);
}).catch(err=>{
console.log(err);
})
//输出: 11 err

中断promise链

let p1 = Promise.resolve(1);
p1.then((value)=>{
console.log(11);
}).then((value)=>{
console.log(22);
}).then((value)=>{
console.log(33);
}).catch(err=>{
console.log(err);
})
//输出:11 22 33

那我们怎么去中断这个回调函数的联调呢

let p1 = Promise.resolve(1);
p1.then((value)=>{
console.log(11);
}).then((value)=>{
console.log(22);
return new Promise(()=>{});
}).then((value)=>{
console.log(33);
}).catch(err=>{
console.log(err);
})
//输出:11 22

答案就是返回一个状态为​​pending​​​的​​promise​​对象

手写Promise

代码逐步迭代,可以对照着看,我把注释都写得比较清晰了~

构造函数实现

我们第一步首先就是完成​​Promise​​​的构造函数,构造函数我们简单去想,其实就是接收一个执行器函数,执行器函数有两个参数,这个方法阔以改变​​Promise​​对象的状态和结果。ok,说干就干!


tips:

  1. 注意!throw err也可以修改Promise的状态与结果!
  2. promise的状态只能修改一次,需要做限制


// 万里长城第一步
function Promise(executor){
this.promiseState = 'pending';
this.promiseResult = null;
const resolve = val => {
// 状态只能修改一次
if(this.promiseState !== 'pending') return;
// 1. 要去修改Promise对象的状态([[promiseState]]),置为成功fulfilled
this.promiseState = 'fulfilled';
// 2. 要去修改Promise对象的状态([[promiseResult]])
this.promiseResult = val;
}

const reject = err => {
// 状态只能修改一次
if(this.promiseState !== 'pending') return;
// 1. 要去修改Promise对象的状态([[promiseState]]),置为失败rejected
this.promiseState = 'rejected';
// 2. 要去修改Promise对象的状态([[promiseResult]])
this.promiseResult = err;
}
// 为什么要加try catch 是因为,throw err也相当于调用reject了【前面说过没看过的去补课】
try{
/*
* 同步执行执行器函数
* 执行器函数接收两个参数,一个是resolve,一个是reject
*/
executor(resolve,reject);
} catch(err) {
reject(err);
}
}

then方法的实现

首先按照之前说过的,then支持两个参数,分别是成功和失败的回调,而且这两个参数可传可不传。

其次,因为异步任务的问题,并且支持多个回调,所以我们需要对回调函数采用数组进行存储,所以引入了新的变量,​​callbackList​

而且我们需要注意then的返回结果也是​​Promise​​对象【不记得的话,回头看看~】

// 万里长城今犹在~
function Promise(executor){
//保存promise状态
this.promiseState = 'pending';
//保存promise结果
this.promiseResult = null;
//用于保存异步回调函数列表
this.callbackList = [];
const resolve = val => {
// 状态只能修改一次
if(this.promiseState !== 'pending') return;
// 1. 要去修改Promise对象的状态([[promiseState]]),置为成功fulfilled
this.promiseState = 'fulfilled';
// 2. 要去修改Promise对象的状态([[promiseResult]])
this.promiseResult = val;
// 调用成功的回调【callbackList存起来的】
for(let callback of this.callbackList){
callback.onResolved(val);
}

}

const reject = err => {
// 状态只能修改一次
if(this.promiseState !== 'pending') return;
// 1. 要去修改Promise对象的状态([[promiseState]]),置为失败rejected
this.promiseState = 'rejected';
// 2. 要去修改Promise对象的状态([[promiseResult]])
this.promiseResult = err;
// 调用失败的回调【callbackList存起来的】
for(let callback of this.callbackList){
callback.onRejected(err);
}
}
// 为什么要加try catch 是因为,throw err也相当于调用reject了【前面说过没看过的去补课】
try{
/*
* 同步执行执行器函数
* 执行器函数接收两个参数,一个是resolve,一个是reject
*/
executor(resolve,reject);
} catch(err) {
reject(err);
}
}
//then方法
Promise.prototype.then = function(onResolved,onRejected){
const self = this;

// then方法会返回Promise
return new Promise((resolve,reject) => {
// 对返回值的处理进行封装
const handleCallback = (callback) => {
// 如果回调函数中抛出错误,则reject
try{
// 需要依据回调的返回结果确定then方法的返回值
// 现在的this会指向return的promise对象,所以使用self
const res = callback(self.promiseResult);
if(res instanceof Promise){
//如果回调返回结果是个Promise
res.then(val => {
resolve(val);
},err => {
reject(err);
})
}else{
// 返回结果不是Promise
resolve(res);
}
}catch(err){
reject(err);
}
}
//调用回调函数
if(this.promiseState === 'fulfilled'){
handleCallback(onResolved);
}
if(this.promiseState === 'rejected'){
handleCallback(onRejected);
}
/*
* 如果是pending状态,则异步任务,在改变状态的时候去调用回调函数
* 所以要保存回调函数
* 因为promise实例阔以指定多个回调,于是采用数组
*/
if(this.promiseState === 'pending'){
this.callbackList.push({
onResolved:() => {
handleCallback(onResolved);
},
onRejected:() => {
handleCallback(onRejected);
}
})
}
})
}

catch方法的实现

我们采用then方法去实现catch方法,但是catch可以处理异常穿透【前面又说哦~】

// 我想找人陪我去长城玩,哈哈哈哈
function Promise(executor){
//保存promise状态
this.promiseState = 'pending';
//保存promise结果
this.promiseResult = null;
//用于保存异步回调函数列表
this.callbackList = [];
const resolve = val => {
// 状态只能修改一次
if(this.promiseState !== 'pending') return;
// 1. 要去修改Promise对象的状态([[promiseState]]),置为成功fulfilled
this.promiseState = 'fulfilled';
// 2. 要去修改Promise对象的状态([[promiseResult]])
this.promiseResult = val;
// 调用成功的回调【callbackList存起来的】
for(let callback of this.callbackList){
callback.onResolved(val);
}

}

const reject = err => {
// 状态只能修改一次
if(this.promiseState !== 'pending') return;
// 1. 要去修改Promise对象的状态([[promiseState]]),置为失败rejected
this.promiseState = 'rejected';
// 2. 要去修改Promise对象的状态([[promiseResult]])
this.promiseResult = err;
// 调用失败的回调【callbackList存起来的】
for(let callback of this.callbackList){
callback.onRejected(err);
}
}
// 为什么要加try catch 是因为,throw err也相当于调用reject了【前面说过没看过的去补课】
try{
/*
* 同步执行执行器函数
* 执行器函数接收两个参数,一个是resolve,一个是reject
*/
executor(resolve,reject);
} catch(err) {
reject(err);
}
}
//then方法
Promise.prototype.then = function(onResolved,onRejected){
const self = this;
//处理异常穿透 并且为onResolved,onRejected设置默认值。因为这两个参数可以都不传
if(typeof onRejected !== 'function'){
onRejected = err => {
throw err;
}
}
if(typeof onResolved !== 'function'){
onResolved = val => val;
}
// then方法会返回Promise
return new Promise((resolve,reject) => {
// 对返回值的处理进行封装
const handleCallback = (callback) => {
// 如果回调函数中抛出错误,则reject
try{
// 需要依据回调的返回结果确定then方法的返回值
// 现在的this会指向return的promise对象,所以使用self
const res = callback(self.promiseResult);
if(res instanceof Promise){
//如果回调返回结果是个Promise
res.then(val => {
resolve(val);
},err => {
reject(err);
})
}else{
// 返回结果不是Promise
resolve(res);
}
}catch(err){
reject(err);
}
}
//调用回调函数
if(this.promiseState === 'fulfilled'){
handleCallback(onResolved);
}
if(this.promiseState === 'rejected'){
handleCallback(onRejected);
}
/*
* 如果是pending状态,则异步任务,在改变状态的时候去调用回调函数
* 所以要保存回调函数
* 因为promise实例阔以指定多个回调,于是采用数组
*/
if(this.promiseState === 'pending'){
this.callbackList.push({
onResolved:() => {
handleCallback(onResolved);
},
onRejected:() => {
handleCallback(onRejected);
}
})
}
})
}

//catch方法
Promise.prototype.catch = function(onRejected) {
// 我们可以直接使用then方法实现
return this.then(undefined,onRejected);
}

Promise.resolve方法实现

简单了,不说啥了

//resolve方法
Promise.resolve = function(val) {
//返回值的情况在前文说过,可以在 Promise的使用一章找到
return new Promise((resolve,reject)=>{
if(val instanceof Promise){
val.then(val => {
resolve(val);
}, err => {
reject(err);
});
}else{
resolve(value);
}
})
}

Promise.reject方法实现

更加简单了,不说啥了

//reject方法
Promise.reject = function(err) {
//返回值的情况在前文说过,可以在 Promise的使用一章找到
return new Promise((resolve,reject)=>{
reject(err);
})
}

Promise.all方法实现

比较简单,回顾之前Promise.all的用法以及返回值,就阔以看懂~

//可以先去回顾一下all方法的用法
//all
Promise.all = function(promiseList) {
let count = 0;
let res = [];
const length = promiseList.length;
return new Promise((resolve,reject)=>{
for(let i = 0;i < length; i++){
promiseList[i].then(val => {
count++;
res[i] = val;
if(count === length){
resolve(res);
}
},err => {
reject(err);
});
}
})
}

Promise.race方法实现

比较简单,回顾之前Promise.race的用法以及返回值,就阔以看懂~

//race
//要结束了!
Promise.race = function(promiseList) {
const length = promiseList.length;
//谁先完成谁就决定结果!
return new Promise((resolve,reject)=>{
for(let i = 0;i < length; i++){
promiseList[i].then(val => {
resolve(val);
},err => {
reject(err);
});
}
})
}

完整代码以及细节处理


tips: 细节回调函数是异步的 我们使用setTimeout进行包裹


function Promise(executor){
//保存promise状态
this.promiseState = 'pending';
//保存promise结果
this.promiseResult = null;
//用于保存异步回调函数列表
this.callbackList = [];
const resolve = val => {
// 状态只能修改一次
if(this.promiseState !== 'pending') return;
// 1. 要去修改Promise对象的状态([[promiseState]]),置为成功fulfilled
this.promiseState = 'fulfilled';
// 2. 要去修改Promise对象的状态([[promiseResult]])
this.promiseResult = val;
setTimeout(() => {
// 调用成功的回调【callbackList存起来的】
for(let callback of this.callbackList){
callback.onResolved(val);
}
})
}

const reject = err => {
// 状态只能修改一次
if(this.promiseState !== 'pending') return;
// 1. 要去修改Promise对象的状态([[promiseState]]),置为失败rejected
this.promiseState = 'rejected';
// 2. 要去修改Promise对象的状态([[promiseResult]])
this.promiseResult = err;
setTimeout(() => {
// 调用失败的回调【callbackList存起来的】
for(let callback of this.callbackList){
callback.onRejected(err);
}
})
}
// 为什么要加try catch 是因为,throw err也相当于调用reject了【前面说过没看过的去补课】
try{
/*
* 同步执行执行器函数
* 执行器函数接收两个参数,一个是resolve,一个是reject
*/
executor(resolve,reject);
} catch(err) {
reject(err);
}
}
//then方法
Promise.prototype.then = function(onResolved,onRejected){
const self = this;
//处理异常穿透 并且为onResolved,onRejected设置默认值。因为这两个参数可以都不传
if(typeof onRejected !== 'function'){
onRejected = err => {
throw err;
}
}
if(typeof onResolved !== 'function'){
onResolved = val => val;
}
// then方法会返回Promise
return new Promise((resolve,reject) => {
// 对返回值的处理进行封装
const handleCallback = (callback) => {
// 如果回调函数中抛出错误,则reject
try{
// 需要依据回调的返回结果确定then方法的返回值
// 现在的this会指向return的promise对象,所以使用self
const res = callback(self.promiseResult);
if(res instanceof Promise){
//如果回调返回结果是个Promise
res.then(val => {
resolve(val);
},err => {
reject(err);
})
}else{
// 返回结果不是Promise
resolve(res);
}
}catch(err){
reject(err);
}
}
//调用回调函数
if(this.promiseState === 'fulfilled'){
setTimeout(()=>{
handleCallback(onResolved);
})
}
if(this.promiseState === 'rejected'){
setTimeout(()=>{
handleCallback(onRejected);
})
}
/*
* 如果是pending状态,则异步任务,在改变状态的时候去调用回调函数
* 所以要保存回调函数
* 因为promise实例阔以指定多个回调,于是采用数组
*/
if(this.promiseState === 'pending'){
this.callbackList.push({
onResolved:() => {
handleCallback(onResolved);
},
onRejected:() => {
handleCallback(onRejected);
}
})
}
})
}
//catch方法
Promise.prototype.catch = function(onRejected) {
// 我们可以直接使用then方法实现
return this.then(undefined,onRejected);
}
//resolve方法
Promise.resolve = function(val) {
//返回值的情况在前文说过,可以在 Promise的使用一章找到
return new Promise((resolve,reject)=>{
if(val instanceof Promise){
val.then(val => {
resolve(val);
}, err => {
reject(err);
});
}else{
resolve(value);
}
})
}

//reject方法
Promise.reject = function(err) {
//返回值的情况在前文说过,可以在 Promise的使用一章找到
return new Promise((resolve,reject)=>{
reject(err);
})
}

//all
Promise.all = function(promiseList) {
let count = 0;
let res = [];
const length = promiseList.length;
return new Promise((resolve,reject)=>{
for(let i = 0;i < length; i++){
promiseList[i].then(val => {
count++;
res[i] = val;
if(count === length){
resolve(res);
}
},err => {
reject(err);
});
}
})
}

//race
Promise.race = function(promiseList) {
const length = promiseList.length;
//谁先完成谁就决定结果!
return new Promise((resolve,reject)=>{
for(let i = 0;i < length; i++){
promiseList[i].then(val => {
resolve(val);
},err => {
reject(err);
});
}
})
}

至此,我们手写Promise的章节也就结束了~

结束语


tips:其实我真的很想吐槽自己,为什么老是在博客结尾要么打鸡血,要么说鸡汤,哈哈


​Promise​​一词本意是承诺。我以这篇文章作为我新的开始,从基础开始沉淀,拒绝好高骛远。

我承诺我的信念与追求会始终如一

我承诺会让自己看遍世间美景

我承诺我我会一直努力成为优秀的工程师

我承诺无论结果如何,喜欢的都会努力地勇敢地追求,不留遗憾

...

最后,愿老朽我永远热血,愿世界永远和平

伙伴们葱呀~ Promise从入门到手写 | [Promise系列一]_回调函数_02

举报

相关推荐

0 条评论