06Promise

50 阅读15分钟

异步任务的处理

众所周知,新技术的出现都是为了解决旧技术的痛点。

function requestDate(url,successCallback,failCallback) {
  // 用 setTimeout 来模拟网络请求
  setTimeout(() => {
    if (url === 'xxx/users') {
      let successMessae = '请求成功';
      successCallback(successMessae)
    } else {
      let failMessage = '请求失败';
      failCallback(failMessage);
    }
  }, 2000);
}


// main.js,代表的是在另外的一个JS模块对 requestDate 进行调用
// 1  对于调用者来说,不论成功或者是失败都是要给一个相应的结果,
// 2  同时传入两个回调函数,在成功的时候执行回调函数一,在失败的时候执行第二个回调函数
requestDate('xxx/users',(res) => {
  console.log(res);
},(err) => {
  console.log(err);
});

小结:

  1. 首先我们先封装一个函数requestDate,用于发送网络请求。
  2. 在调用requestDate这个函数的时候,我们还需要传入两个回调函数successCallbackfailCallback
  3. 当请求成功的时候,我们执行第一个回调函数,当请求失败的时候,执行第二个回调函数,将成功和失败的值,通过回调函数传递出去。

弊端:

  1. 如果是我们自己封装的requestDate,那么我们在封装的时候必须要自己设计好callBack函数的名称,并且要使用好。
  2. 如果我们使用的是别人所封装的requestDate或者一些第三方的库,那么我们可能就需要去看源码或者文档,才知道回调函数的名称。
function requestDate2 () {
  return '承诺';
}
const chengnuo = requestDate2();
console.log(chengnuo);

所谓的承诺,就是在成功的时候,告诉你成功了,失败的时候告诉你失败了,承诺(规范好了代码的编写逻辑)。而我们所说的Promise就是这样的一种规范。

什么是Promise?

Promise是一个类,可以翻译成为:承诺、许诺、契约。

  1. 当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象。

在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor;

  1. 这个回调函数会被立即执行,并且这个回调函数,需要传入另外两个回调函数resolvereject;
  2. 当我们调用resolve回调函数时候,会执行Promise对象的then方法传入的回调函数。
  3. 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;

Promise的状态

Promise在使用过程中,我们大概可以分为三个状态:

  1. 待定(pending): 初始状态,既没有被兑现,也没有被拒绝,当执行executor(执行器/执行者)中的代码时,处于该状态。
  2. 已兑现(fulfilled): 意味着操作成功完成,执行了resolve时,处于该状态。
  3. 已拒绝(rejected): 意味着操作失败,执行了reject时,处于该状态。
// 对于这个代码,是会直接进行调用的。
const f2 = new Promise(() => {
  console.log('结束了');// 结束了
})

const f1 = new Promise((resolve,reject) => {
  resolve('成功');
  reject('失败');
});
f1.then((res) => {
  console.log(res);// 成功
}).catch((err) => {
  console.log(err); // 不会执行
});

要注意的是Promise的状态在改变之后,是不会继续发生改变的,所以当状态变为resolve状态后,下面调用reject是没有任何效果的。

当我们调用resolve的时候,外面的.then这个回调函数就会被执行,reject.catch同样是如此。

Promise处理异步数据

function requestData(url) {
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      if (url === 'xxx.user/info') {
        // 取到成功的结果
        let data = '成功的消息';
        resolve(data);
      }else{
        // 失败的结果
        let errData = '错误的消息';
        reject(errData);
      }
    },3000);
  });
};

const r1 = requestData('xxx.user/info');
r1.then((res) => {
  console.log(res);
});

// r1里面其实是可以放两个回调的。
r1.then((res) => {
  //成功的回调
},(err) => {
  // 失败的回调
})

对于异步请求的代码,我们都会放入到Promiseexecutor(执行器)里面,然后通过调用函数的resolvereject两个办法,来将参数传递出去。

当然要记得.then里面是可以放两个回调的,第一个是成功的回调,第二个则是失败的回调。

resolve详解

1 直接调用resolve,并没有传递值的时候。

// promise 刚开始的状态叫做:pengding状态,或者称为待定状态。
const p1 = new Promise((resolve,reject) => {
 console.log('promise开始');//会直接执行
 resolve();
 console.log('er');//这个代码也是会执行的,同样是同步代码。
});

p1.then((res) => {
  console.log('res',res);// res undefined
},(err) => {
 console.log('err',err);
})

首先,在Promise里面写的代码会直接执行,相当于是同步代码。然后当然我们直接调用resolve函数,并且不给他传递参数的时候,我们所接收到的res则是一个undefined值,而这个时候则是成功的状态(fulfilled),或者叫做已兑现的状态。

2 传递的是普通的值或者对象。

const p1 = new Promise((resolve,reject) => {
 console.log('promise开始');//会直接执行
 resolve({message:'cs2'});
});

p1.then((res) => {
  console.log('res',res); // res { message: 'cs2' }
},(err) => {
 console.log('err',err);
})

传入普通的值和对象,其实没有太大的区别,我们在接收的时候就能够发现,而数组同样也是如此。

3 传入一个Promise,那么当前Promise的状态,会由传入的Promise来进行决定。这个时候相当于将状态进行了一个移交。

const newPro= new Promise((resolve,reject) => {

});

const p1 = new Promise((resolve,reject) => {
 console.log('----');//会执行
  resolve(newPro);
  console.log('e----');//会执行
})
p1.then((res)=>{
  console.log('res',res);
},(err) => {
  console.log('err',err);
})

当我们传入一个Promise的时候,能够发现什么事情都没有发生,而且虽然我们调用了resolve方法,但是p1的状态依旧是pengding状态。

const newPro= new Promise((resolve,reject) => {
  resolve();
  // reject();
});

const p1 = new Promise((resolve,reject) => {
  console.log('----');
  resolve(newPro);
  console.log('e----');

})
p1.then((res)=>{
  console.log('res',res);
},(err) => {
  console.log('err',err);
})

当我们改变传入的Promise状态时候,当前的Promise也会随着改变自身的状态。

4 传入一个对象,并且这个对象有then方法。那么就会执行该对象的then方法,并且状态是由这个then方法所决定的。

const p1 = new Promise((resolve,reject) => {
  const obj = {
    // then会接收两个参数:resolve,reject
    then:function (resolve,reject) {
      // 在这里决定promise的状态,和传递的值
      resolve('nnn');
    }
  }
  resolve(obj);
})
p1.then((res)=>{
  console.log('res',res);//res => nnn
},(err) => {
  console.log('err',err);
})

就像上面所写的一样,如果对象里面有then方法的时候,就需要换一个角度去考虑了,而且该then方法会自动执行,但是单纯的在对象里面写then方法是没有任何效果的,也不会自动执行。这是promise所造成的效果。

then方法

then方法是Promise对象上的一个方法:它其实是放在Promise的原型上的,就比如说是Promise.prototype.then

then方法接收两个回调函数:

  1. fulfilled的回调函数:当状态变成fulfilled时会回调的函数。
  2. reject的回调函数:当状态变成reject时会回调的函数。
const p1 = new Promise((resolve,reject) => {

})
p1.then((res) => {
  console.log(res);
},(err) => {
  console.log(err);
})

// 相当于
p1.then(res => {
  console.log(res);
}).catch((err) => {
  console.log(err);
})

then里面的两个回调函数,与thencatch的回调函数是一样的。

then方法的多次调用

then方法是可以进行多次调用的。

const p1 = new Promise((resolve,reject) => {
  resolve('你好');
});
p1.then((res) => {
  console.log("res1",res);
});
p1.then((res) => {
  console.log("res2",res);
});
p1.then((res) => {
  console.log("res3",res);
});

就如同上面一样的去调用,但是对于我们来说,三次调用所接收到的值都是一样的。

then方法的返回值

const p1 = new Promise((resolve,reject) => {
  resolve('你好');
});
p1.then((res) => {
  console.log("res1",res);
  return 123
});
// promise链式调用时候,第二个then方法,就是看第一个then方法的返回值

所谓的then方法的返回值,是指的then方法里面回调函数的返回值。

1 当返回值是一个普通类型(数字/字符串/普通对象/undefined)的时候。

p1.then((res) => {
  return '123'
});
// 相当于,
p1.then((res) => {
	return new Promise((resolve,reject) => {
		resolve('aaaa');
	}) 
});

当返回的是一个普通值的时候,那么这个普通的值会被作为一个新的Promiseresolve的值。然后作为then方法的返回值进行一个返回。

then方法本身也是有返回值的,它的返回值是Promise

p1.then((res) => {
  return '123'
}).then((res) => {
	console.log(res); // '123'
})

// 多一点,链式调用
p1.then((res) => {
  return '123'
}).then((res) => {
	console.log(res); // '123';
  return 'bbbb'
}).then((res) => {
  console.log(res); // bbb
})

所以说我们可以继续进行then方法的调用,而这里的第二个then方法并不是对p1的调用,而是p1.then返回值的调用。

对于一个函数来说,如果没有返回值,那么返回的就是undefined了。

2 返回值是Promise类型。

const p1 = new Promise((resolve,reject) => {
  resolve('aaa')
});
p1.then((res) => {
  console.log("res1",res);
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve('bbb')
    },3000);
  })
}).then((res) => {
  console.log(res);
})

//两者的比较 
p1.then((res) => {
  return '123';
  // 相当于
  return new Promise((resolve,reject) => {
    resolve('123');
  })
})
p1.then((res) => {
  return new Promise((resolve,reject) => {
		resolve(new Promise((resolvee,reject) => {
      resolve()
    }));
	})
})

对于普通的值来说,resolve的是某个字符串或者数字,而对于Promise来说这里resolve的就是这个Promise,那么外面的resolve的值,将由里面的Promise的状态来决定,只有当里面的Promise在调用resolve的时候,外面的Promise才会真正的调用resolve方法。

3 如果返回的是一个对象,并且改对象实现了 thenable

const p1 = new Promise((resolve,reject) => {
  resolve('aaa')
});
p1.then((res) => {
  return {
    then:function (resolve,reject) {
      resolve('bbbb');
    }
  }
}).then((res) => {
  console.log(res); // bbbb
})

当对象里面有then方法的时候,then所对应的函数,会接收resolvereject两种状态,然后通过调用其中的某一个方法,来改变Promise的状态,并且将值给传递出去。

catch方法

const p1 = new Promise((resolve,reject) => {
  reject('err message');
})
// 简单的使用
p1.catch(err => {
  console.log(err); //err message
})

//使用 throw Error
const p1 = new Promise((resolve,reject) => {
  // reject('err message');
  throw new Error('错误信息');
})

p1.catch(err => {
  console.log(err); // 错误信息
  console.log('eeeeeeee');// eeeeeeee
})

当我们调用catch方法的时候,我们就会接收到这个回调,同样当我们使用throw Error的时候,catch同样也能够捕获到错误的信息。

但是不符合Promise a+规范。因为规范里面错误捕获要写在then的第二个回调里面,在ES6中为了更加的方便处理,所以写在了catch里面,效果是完全一样的。

p1.then(res => {
  return 111
}).catch(err => {
  
})

当我们这样去捕获异常的时候,前面说到then会返回一个新的promise对象,那么我们到底捕获的是原来的promise,还是新的promise呢?其实在内部实现的时候,它是捕获原来的promise,而不是then出来这个新的promise

所以在promise a+规范里面是没有catch的,而对于catch来说,优先捕获原本promise的异常,如果没有捕获到的话,那么就会捕获then返回的这个promise的异常。

const p1 = new Promise((resolve,reject) => {
  reject('err message');
})
p1.then(res => {
  throw new Error('错误');
}).catch(err => {
  console.log(err);//err message
})

那么当catch捕获到原来promise的异常,那么then里面的异常就会捕获不到了。

p1.then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);//err message
  return 'catch1'
}).then((res) => {
  console.log(res);//catch1
})

catch后面继续加一个then的时候。catch里面return出来的值,就是then所接收的值了。

finally方法

finally是在ES9(2018)中新增的新增的一个特性:表示无论promise对象变成fulfilled还是reject状态,最终都会被执行的代码。

finally方法是不接收参数的,因为无论前面是fulfilled还是reject状态,它都会执行。

const p1 = new Promise((resolve,reject) => {
  resolve('1111')
});

p1.then((res) => {
  console.log(res);// 111
}).catch((err) => {
  console.log(err);
}).finally(() => {
  console.log('我的世界如此奇妙'); // 我的世界如此奇妙
})

// 当调用 reject 的时候
const p1 = new Promise((resolve,reject) => {
  reject('err message');
});

p1.then((res) => {
  console.log(res);
}).catch((err) => {
  console.log('1',err); // 1 err message
}).finally(() => {
  console.log('我的世界如此奇妙'); // 我的世界如此奇妙
})

简单来说就是放在某个Promise的最后,不论Promise的状态变成什么,这个回调里面的代码都会被执行。

resolve方法

前面所学习的thencatchfinally方法属于Promise实例方法,都是放在prototype上面的,而现在所说的都是promise的类方法。

Promise.resolve("hello world");

// 相当于 _ 可以当做占位符号
new Promise((_,reject) => {
  resolve("hello world")
})
const obj1 = {
  name1:'cs',
  age:18
};

// 现在我们是有一个对象,要将他转成promise对象
const p1 = Promise.resolve(obj1);
p1.then(res => {
  console.log(res);
})

有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方 法来完成。Promise.resolve的用法相当于new Promise,并且执行resolve操作。

reject方法

reject方法和resolve方法是差不多的。

const message = 'err message236';

const p1 = Promise.reject(message);
p1.then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})

Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的

all方法

作用:将多个Promise包裹在一起,形成一个新的Promise

const p1 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('1');
  },1000);
})
const p2 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('2');
  },2000);
})
const p3 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('3');
  },3000);
})
// all里面也可以传递非promise的值,会自动转化为promise,Promise.resolve('aaa')
Promise.all([p1,p2,p3,'aaa']).then(res => {
  console.log(res); // [ '1', '2', '3', 'aaa' ]
})

Promise的状态,由所包裹的所有Promise共同决定。当所有的Promise状态变成fulfilled状态时,新Promise的状态会变成fulfilled,并且将所有的Promise的返回值组成一个数组。

const p1 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('1');
  },1000);
})
const p2 = new Promise((resolve,reject) => {
  setTimeout(() => {
    reject('2');
  },2000);
})
const p3 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('3');
  },3000);
})

Promise.all([p1,p2,p3,'aaa']).then(res => {
  console.log(res); 
}).catch(err => {
  console.log(err);// 2; 2秒的时候出现reject的结果,等待5秒后,所有代码执行完成。
})

当有一个Promise的状态为reject的时候,新Promise的状态也为reject,同时会将第一个reject的返回值作为参数。

allSettled方法

all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;

const p1 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('1');
  },1000);
})
const p2 = new Promise((resolve,reject) => {
  setTimeout(() => {
    reject('2');
  },2000);
})
const p3 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('3');
  },5000);
})

Promise.allSettled([p1,p2,p3,'aaa']).then(res => {
  console.log(res); 
}).catch(err => {
  console.log(err); // 不会有任何的值
})
// res 的值如下所示
[
  { status: 'fulfilled', value: '1' },
  { status: 'rejected', reason: '2' },
  { status: 'fulfilled', value: '3' },
  { status: 'fulfilled', value: 'aaa' }
]

在ES11(ES2020)中,添加了新的API Promise.allSettled:该方法会在所有的Promise都有结果,无论是fulfilled,还是reject时,才会有最终的状态;并且这个Promise的结果一定是fulfilled的;

race方法

如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;

const p1 = new Promise((resolve,reject) => {
  setTimeout(() => {
    reject('1');
  },1000);
})
const p2 = new Promise((resolve,reject) => {
  setTimeout(() => {
    reject('2');
  },2000);
})
const p3 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('3');
  },5000);
})

// 只要有一个 promise 的状态变成 resolve或者reject ,那么就会结束
Promise.race([p1,p2,p3]).then(res => {
  console.log('r',res);
}).catch(err => {
  console.log('e',err);
})

根据第一个来决定,Promise.race所返回的Promise的状态。

any方法

any方法是ES12中新增的方法,和race方法是类似的:any方法会等到一个fulfilled状态,才会决定新Promise的状态;如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;

const p1 = new Promise((resolve,reject) => {
  setTimeout(() => {
    reject('1');
  },1000);
})
const p2 = new Promise((resolve,reject) => {
  setTimeout(() => {
    reject('2');
  },200);
})
const p3 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('3');
  },5000);
})

// any 方法
Promise.any([p1,p2,p3]).then((result) => {
  console.log(result);
}).catch((err) => {
  console.log(err);
});

Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。

promise

promise A+规范网站:promisesaplus.com/。

// 定义Promise的状态
const STATUS_PENDING = 'pending';
const STATUS_RESOLVE = 'resolve';
const STATUS_REJECT = 'reject';
class CSPromise{
  constructor(executor){
    this.status = STATUS_PENDING;
    const resolve = (value=undefined) => {
      // 只有当状态为 pending 的时候,才能够改变状态
      if (this.status === 'pending') {
        // queueMicrotask 将一个任务加入到微任务队列里面
        this.status = STATUS_RESOLVE;
        queueMicrotask(() => {
           //改变为resolve状态
          // 然后执行代码
          console.log('resolve被调用',value);
          // 然后要执行的是 then 传递进来的回调函数
          this.onfulfilled(value);
        })
      }
    };
    const reject = (reason=undefined) => {
      this.status = STATUS_REJECT;
      if (this.status === 'pending') {
        queueMicrotask(() => {
          // 改变为 reject 状态
          // 然后执行代码
          console.log('reject被调用',reason);
          // 然后要执行的是 then 传递进来的回调函数
          this.onrejected(reason);
        })
      }
    };
    executor(resolve,reject);
  }
  // 成功和失败
  then(onfulfilled,onrejected){
    this.onfulfilled = onfulfilled;
    this.onrejected = onrejected;

  }

  
}

const p1 = new CSPromise((resolve,reject) => {
  // 1 这里面的东西会直接被调用的。
  console.log('直接开始');

  // 2 调用resolve或者reject
  // resolve('xxx');
  // reject()

  // 3 调用 resolve 或者 reject的时候,传递参数
  // resolve('123');//123
  // resolve(); // undefined
  // reject() // undefined
  // reject('456') // 456
  resolve('123');
  reject('456')

})

// 2 这里涉及到 当调用 resolve 或者 reject在调用之后,不能调用另外一个,所以就需要 状态来进行管理


// then 回调的实现
p1.then((res) => {
  console.log('res',res);
})

Promise的简单实现,其中很多的东西都没有完善,比如说多次的调用、链式的调用等等。