前言
相信大家对Promise一定不陌生,在项目开发中会经常使用到它。但它内部究竟是如何实现的?
1. Promise的基本使用
javaScript的一大特点就是单线程,为了不阻塞主进程的工作,
一些耗时的操作(如网络请求)一般都是放在异步队列中去异步执行。
但是这样也就产生了很大的问题,一些ajax请求依赖于上一个ajax请求的返回值,就出现了臭名昭著的地狱回调问题。
Promise就是为了解决这种现象应运而生的
Promise是一个构造函数,必须接收一个函数作为参数,这个函数我们一般称为executor,executor函数也接收两个参数,resolve、reject,这两个参数也是函数.
let p1 = new Promise(function(resolve,reject){
// 这里编写异步操作的代码
setTimeout(function(){
resolve(1)
})
})
这里,我们得到了p1这个对象,可以通过p1.then对拿到的结果进行处理 then函数接受两个函数类型的参数,第一个是onFulfilled(Promise内部的状态为fulfilled时,后续会讲到这个状态,这里暂且理解为成功拿到结果之后执行),第二个是onRejected(Promise内部的状态为rejected时,这里理解为结果错误的情况下执行)
p1.then(function(data){
console.log(data)
},function(err){
console.log(err)
})
2. Promise的核心概念
Promise对象内部有三种状态
-
pending(进行中)
-
fulfilled(已成功)
-
rejected(已失败) promise的状态只由异步处理的结果来改变,外界无法改变它。初始状态为pending,异步操作成功之后执行了resolve函数,promise的状态由pending变为fulfilled,如果异步操作失败,则promise的状态由pending变为rejected,promise的状态一经改变之后就不可更改。
3. Promise简易实现
学到现在,我们对promise的基本结构有了一定的了解,
promise是一个构造函数,接收一个函数作为参数,内部有个状态标识,有两个可以改变状态的方法,
resolve: 执行之后状态由pending变为fulfilled
reject: 执行之后状态由pending变为rejected
还有一个then方法,接收成功和失败之后的回调函数
//下面我们尝试自己写一个实现上述功能的promise吧~
// 定义Promise的三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise{
constructor(executor){
if(typeof executor !== 'function'){
throw new TypeError('executor must be a function!')
}
this.status = PENDING; //初始状态
this.value = undefined;
this.reason = undefined;
const resolve = function(res){
if(this.status === PENDING){
this.status = FULFILLED;
this.value = res;
}
}
const reject = function(err){
if(this.status === PENDING){
this.status = REJECTED;
this.reason = err
}
}
//try catch用来捕获执行函数执行过程中报的错
try{
executor(resolve,reject)
}catch(err){
//如果有错误,就执行reject
reject(err)
}
}
then(onFulfilled,onReject){
if(this.status === FULFILLED){
onFulfilled(this.value)
}
if(this.status === REJECTED){
onFulfilled(this.message)
}
}
}
上述代码带大家简单的了解了promise内部的执行结构以及状态变化,
但是并不支持异步和链式调用,我们接着更近一层吧~
4. Promise如何实现异步处理和链式调用
- 异步 异步就是在执行promise.then()的时候,由于异步还没有执行完,所以,promise的状态还未改变,所以上面的代码只判断了status状态改变后执行就不行了,then里面的代码就不会执行了。而且then方法可以被同一个promise多次调用,我们可以创建一个回调的数组用来存放状态改变时需要执行的回调,在promise的状态改变时,再执行这些回调,到此为止,该Promise可以正常处理同步和异步的操作了
异步的处理是利用发布订阅的模式实现的,在then函数里,当promise是pending状态,收集(成功/失败)状态下需要执行的函数,放入成功或失败的回调数组里,当promise内部状态改变的时候,从根据当前的状态依次执行(成功/失败)回调数组里收集的函数需要注意:对同一个promise执行了多次then,那么then里面的回调函数要不都走的是成功的逻辑要不都走的是失败的逻辑!
class Promise{
constructor(executor){
if(typeof executor !== 'function'){
throw new TypeError('executor must be a function!')
}
this.status = PENDING; //初始状态
this.value = undefined;
this.reason = undefined;
//onFulfilledCallbacks用来收集FULFILLED状态时要执行的回调函数
this.onFulfilledCallbacks = [];
//onRejectedCallbacks用来收集REJECTED状态时要执行的回调函数
this.onRejectedCallbacks = [];
const resolve = function(res){
if(this.status === PENDING){
this.status = FULFILLED;
this.value = res;
}
let fn;
// 一次执行队列中的函数,并清空队列 发布
while(fn = this.onFulfilledCallbacks.shift()){
fn();
}
}
const reject = function(err){
if(this.status === PENDING){
this.status = REJECTED;
this.reason = err
}
let fn;
while(fn = this.onRejectedCallbacks.shift()){
fn();
}
}
}
try{
executor(resolve,reject)
}catch(err){
reject(err)
}
}
then(onFulfilled,onReject){
if(this.status === PENDING){
//订阅
this.onFulfilledCallbacks.push(()=>{
onFulfilled(this.value)
});
this.onRejectedCallbacks.push(() => {
onReject(this.reason)
});
}
if(this.status === FULFILLED){
onFulfilled(this.value)
}
if(this.status === REJECTED){
onFulfilled(this.reason)
}
}
}
- 链式调用
Promise的then方法时可以支持链式调用的,即
promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2)
,所以then方法必须返回一个新的Promise对象,且promise的链式调用,必须保证在当前的promise的状态为fulfilled后,才会去执行下一个promise
then(onFulfilled,onReject){
// * 如果传入的回调不是函数的花,会有一个默认的回调,返回promise的值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
onReject = typeof onReject === 'function' ? onReject : (err) => err;
// new Promise会立即执行executor,所以可以把then方法中的逻辑放入executor函数中
let promise2 = new Promise((resolve,reject) => {
if( this.status === PENDING){
//订阅
this.onFulfilledCallbacks.push(()=>{
try {
resolvePromise( this.value,promise2, onFulfilled,resolve,reject)
} catch (error) {
reject(error)
}
});
this.onRejectedCallbacks.push(() => {
try {
resolvePromise( this.reason,promise2,onReject,resolve,reject)
} catch (error) {
reject(error)
}
});
}
if( this.status === FULFILLED){
setTimeout(() => {
try {
resolvePromise(this.value,promise2, onFulfilled,resolve,reject)
} catch (error) {
reject(error)
}
},0)
}
if(this.status === REJECTED){
setTimeout(() => {
try {
resolvePromise( this.reason,promise2,onReject,resolve,reject)
} catch (error) {
reject(error)
}
},0)
}
})
return promise2;
}
//辅助方法resolePromise
function resolvePromise(value,promise2,callback,resolve,reject){
// 执行回调,拿到回调的返回
let x = callback(value);
if(x === promise2){
return reject(new TypeError('TypeError'))
}
// 用来解决promise的状态一旦发生改变,就不会再执行任何改变状态的操作了
let called = false;
if(typeof x === 'function' || (typeof x === 'object' && x !== null)){
try {
let then = x.then
// 如果res有then方法,基本可以确定返回值是一个promise对象
if(typeof then === 'function'){
if(called) return true
then.call(x, (y) => {
called = true
// resolve(y)
// 递归调用,解决resolve()传入的参数仍然是一个promise的问题
resolvePromise(y,promise2,(value) => value,resolve,reject)
}, (r)=> {
called = true
reject(r)
});
}else {
resolve(x)
}
} catch (error) {
reject(error)
}
}else {
resolve(x);
}
}
学到现在,Promise的内部实现原理基本就很清晰了,then函数的链式调用实现原理有些复杂,核心就是then方法需要返回一个新的promise,且必须得保证上一个promise的状态改变之后才会执行下一个promise。了解promise的内部状态及其改变逻辑,会比较好理解一些~
Promise的其他API
- catch catch是一种语法糖写法,本身执行的是then(undefined,(err)=>{}),由于Promise源码中的代码逻辑都在try catch中,所以一旦程序运行错误,都会执行错误监听的回调函数中。
catch(onError){
this.then(undefined,onError)
}
- finally
finally是不管promise最终的状态如何,都会执行该方法。
finally中的回调函数不接受任何的参数,说明finally函数的执行与状态无关
// 需要一个静态的resolve方法,返回一个Promise
static resolve(val){
const p = new Promise(function(){});
p.status = 'fulfilled';
p.value = val;
return p;
}
finally(cb){
return this.then(function(res){
res => Promise.resolve(cd()).then(() => return res)
},function(err){
res => Promise.resolve(cd()).then(() => return err)
})
}
- 静态方法all Promise的all方法,用于将多个promise实例,封装成一个promise对象,当传入的所有promise实例的状态都改为fulfilled,当前的promise对象的状态才会变为fulfilled,当传入的promise实例中的其中一个状态变为rejected,当前promised对象的状态变为rejected,q且此时的promise对象的值为状态变为rejected的promise实例的值
Promise.all = function(arr){
return new MyPromise(function(resolve,reject){
var args = [];
var len = arr.length - 1;
function handler(val,i){
if(typeof val === 'function' || typeof val === 'object'){
if(val instanceof MyPromise){
val.then(res => {
args[i] = res
},err => {
reject(err)
})
}
}
args[i] = val;
if(i === len){
resolve(args)
}
}
arr.forEach((item,i) => {
handler(item,i)
})
})
}
- 静态方法race race方法简单理解为就是赛跑,参数传入多个promise实例,哪个promise实例的状态先改变,返回的promise对象的状态就改变
Promise.race = function(arr){
return new MyPromise(function(resolve,reject){
arr.forEach(item => {
Promise.resolve(item).then(res => {
resolve(res);
},function(err){
reject(err);
})
})
})
总结
花了很长的时间,总结输出,过程中也自己也重新理解了不少。文章中有不足之处,还望大家指出赐教!
完结撒花🎉🎉🎉