promise由浅入深(附源码)

我心飞翔 分类:javascript

前言

相信大家对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变为rejectedpromise的状态一经改变之后就不可更改

3. Promise简易实现

 学到现在,我们对promise的基本结构有了一定的了解,
 promise是一个构造函数,接收一个函数作为参数,内部有个状态标识,有两个可以改变状态的方法,
 resolve: 执行之后状态由pending变为fulfilled
 reject: 执行之后状态由pending变为rejected
 还有一个then方法,接收成功和失败之后的回调函数
 
//下面我们尝试自己写一个实现上述功能的promise吧~
class Promise{
    constructor(executor){
        if(typeof executor !== 'function'){
            throw new TypeError('executor must be a function!')
        }
        this.status = 'pending';
        this.value = undefined;
        this.message = undefined;
        this.resolve = function(res){
        if(this.status === 'pending'){
            this.status = 'fulfilled';
            this.value = res;
        }
        this.reject = function(err){
            if(this.status === 'pending'){
             this.status = 'reject';
             this.message = err
            }
        }
        }
        executor(resolve,reject)
    }
    then(onFulfilled,onReject){
        if(this.status === 'fulfilled'){
            onFulfilled(this.value)
        }
         if(this.status === 'reject'){
            onFulfilled(this.message)
        }
    }
}
 
上述代码带大家简单的了解了promise内部的执行结构以及状态变化,
但是并不支持异步和链式调用,我们接着更近一层吧~
 

4. Promise如何实现异步处理和链式调用

  • 异步

异步就是在执行promise.then()的时候,由于异步还没有执行完,所以,promise的状态还未改变,所以上面的代码只判断了status状态改变后执行就不行了,then里面的代码就不会执行了。而且then方法可以被同一个promise多次调用,我们可以创建一个回调的数组用来存放状态改变时需要执行的回调,在promise的状态改变时,再执行这些回调,到此为止,该Promise可以正常处理同步和异步的操作了

    class Promise{
constructor(executor){
if(typeof executor !== 'function'){
throw new TypeError('executor must be a function!')
}
this.status = 'pending';
this.value = undefined;
this.message = undefined;
this.fulfilledCallbacks = [];
this.rejectedCallbacks = [];
this.resolve = function(res){
if(this.status !== 'pending') return;
if(this.status === 'pending'){
this.status = 'fulfilled';
this.value = res;
}
let fn;
// 一次执行队列中的函数,并清空队列
while(fn = this.fulfilledCallbacks.shift()){
fn(this.value);
}
this.reject = function(err){
if(this.status === 'pending'){
this.status = 'reject';
this.message = err
}
let fn;
while(fn = this.rejectedCallbacks.shift()){
fn(this.err);
}
}
}
executor(resolve,reject)
}
then(onFulfilled,onReject){
if(this.status === 'pending'){
this.fulfilledCallbacks.push(onFulfilled);
this.rejectedCallbacks.push(onReject);
}
if(this.status === 'fulfilled'){
onFulfilled(this.value)
}
if(this.status === 'reject'){
onFulfilled(this.message)
}
}
}
  • 链式调用

Promise的then方法时可以支持链式调用的,即promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2),所以then方法必须返回一个新的Promise对象,且promise的链式调用,必须保证在当前的promise的状态为fulfilled后,才会去执行下一个promise

        //我们来对then方法做进一步的改进
then(onFulfilled,onReject){
const {value,status,message,fulfilledCallbacks,rejectedCallbacks} = this
return new Promise(function(resolve,reject){
let resolveHandle = function(value){
let res = onFulfilled(value);
// 对onFulfilled执行完的结果进行判断,如果返回的时一个promise实例
if(res instanceof Promise){
//res.then将resolve和reject传入
res.then(resolve,reject)
}else{
// 如果返回的是一个普通值,调用resolve,并将onFulfilled执行之后的值作为参数
// 执行之后,改变当前promise的状态
resolve(res)
}
}
let rejectHandle = function(err){
let res = onReject(err)
if(res instanceof Promise){
res.then(resolve,reject)
}else{
// 如果返回的是一个普通值,将返回值作为参数执行得到结果
reject(res)
}
}
if(status === 'pending'){
fulfilledCallbacks.push(resolveHandle);
rejectedCallbacks.push(rejectHandle);
}else if(status === 'fulfilled'){
onFulfilled(value);
}else if(status === 'rejected'){
onReject(message)
}
})
}

学到现在,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);
})
})
})

总结

花了很长的时间,总结输出,过程中也自己也重新理解了不少。文章中有不足之处,还望大家指出赐教!
完结撒花🎉🎉🎉

src=http___www.p5w.net_kuaixun_hdshf_201804_W020180423551301258102.jpg&refer=http___www.p5w.jpg

回复

我来回复
  • 暂无回复内容