前言:
本文将包括:
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:
- 注意!throw err也可以修改Promise的状态与结果!
- 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的章节也就结束了~