美文网首页
js 异步系列(2)-promise上

js 异步系列(2)-promise上

作者: Super曲江龙Kimi | 来源:发表于2019-08-18 12:33 被阅读0次

promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

promise用法可以参考:promise用法
promise/A+ 规范: promise/A+
promise/A+中文翻译: promise/A+中文

简单的使用就不在探究了。可以自行查看文档。这里只把使用上表现结合起来探究其原理,并手写一个Promise

promise主体

根据promise/A+规范

  1. promise是一个类。
  2. 每次new一个Promise时都需要传递一个执行器。是立即执行的
  3. 执行器中有两个参数resolve、reject
  4. 默认promise中有是三个状态,pendding、fulfilled、rejected
    pendding -> resolve 就代表成功fulfilled
    pendding -> reject 就代表成功rejected
  5. 如果一旦状态改变为成功或失败,就不能再变回失败或成功了
  6. 每个promise都有一个then方法

先根据如下规范可以写出promise的大体框架

 const PENDING = 'PENDING'
 const FULFILLED = 'FULFILLED'
 const REJECTED = 'REJECTED'

class Promise {
    constructor(executor) {
        // 保存成功后的信息 resolve(222)  -> 222
        this.value = undefined;
        // 保存失败后的错误 reject(333)  -> 333
        this.reason = undefined;
        // 保存promise状态
        this.status = PENDING;
        // 调用resolve()时的回调
        let resolve = value => {
              this.value = value;
              this.status = FULFILLED;
        } 
        // 调用reject()时的回调
        let resolve = reason => {
              this.reason = reason;
              this.status = REJECTED;
        } 
        // new Promise(executor) 立即执行执行器,传入回调
        executor(resolve, reject);
    }
    
    // 外界调用的then函数
    then(onFulfilled, onRejected) {
        // 当状态为成功时,调用p.then((res) => {})传入的第一个回调
        if (this.status === FULFILLED) {
           onFulfilled(this.value);
        }
        // 当状态为成功时,调用p.then(null, (res) => {})传入的第二个回调
        if (this.status === REJECTED) {
           onRejected(this.reason);
        }
    }
}

1.执行器executor在new Promise时是立即执行的。

new Promise((resolve, reject) => {
    console.log(1)
})
console.log(2)
// 1
// 2

new Promise((resolve, reject) => {
    setTimeout(() => {console.log(1)})
})
console.log(2)
// 2
// 1

2. 规范中规定: 如果一旦状态改变为成功或失败,就不能再变回失败或成功了

所以代码需要改写,增加判断当前状态是不是PENDDING,如果是才能修改状态

// 调用resolve()时的回调
let resolve = value => {
  if(this.status === PENDDING) {
      this.value = value;
      this.status = FULFILLED;
  }
} 
// 调用reject()时的回调
let resolve = reason => {
  if(this.status === PENDDING) {
      this.reason= reason;
      this.status = REJECTED;
  }
} 

3. 在使用中不关使用reject()可以抛出错误,直接使用throw new Error也可以进入rejected逻辑

let p = new Promise((resolve)=>{throw new Error(666)})
p.then(null, err => {console.log(err)})
// vendor.7ab21e702e7733b6b702.js:1 Error: 666

所以需要在调用执行器时加入try... catch

 // new Promise(executor) 立即执行执行器,传入回调
    try {
        executor(resolve, reject);
    } catch(e) {
        // 如果有throw的错误,直接执行reject回调
        reject(e);
    }

4. 如果执行器中有异步操作。则调用then时有可能还是pendding状态。

let p = new Promise(resolve => {
    setTimeout(() => {
        resolve(666)
    })
    // 或者ajax请求等异步操作
});

所以需要增加逻辑判断。并利用发布订阅。在构造函数中定义onResolvedCallbacks和onRejectedCallbacks队列,如果是pendding状态则将回调push入队列中。在异步执行完毕调用resolve()或者reject()时。去回调中执行。

constructor {
    // 再定义两个队列
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    
// 调用resolve()时的回调

    let resolve = value => {
        if(this.status === PENDDING) {
            this.value = value;
            this.status = FULFILLED;
            // 一旦调用resolve() 就把之前保存的异步回调都执行一遍
            this.onResolvedCallbacks.forEach(fn => fn());
        }      
    } 
    // 调用reject()时的回调
    let resolve = reason => {
        if(this.status === PENDDING) {
            this.reason= reason;
            this.status = REJECTED;
            // 一旦调用reject() 就把之前保存的异步回调都执行一遍
            this.onRejectedCallbacks.forEach(fn => fn());
        }
    } 
}
then(onFulfilled, onRejected) {
    if (this.status === PENDDING) {
        this.onResolvedCallbacks.push(() => {
            // todo
            onFulfilled(this.value);
        })
        this.onRejectedCallbacks.push(() => {
            // todo
            onRejected(this.reason);
        })
    }
}

回调为什么要定义成队列数组呢?
因为一个promise实例有可能有多个then调用。

let p = new Promise(resolve => {resolve(43)});

p.then(res => {console.log(1)});
p.then(res => {console.log(2)});
p.then(res => {console.log(3)});

// 上面一个实例p,有三个then函数调用。所以会把他的onFulfilled回调都放入数组中

5. 如果then中传入的不是函数

如果then中传入的不是函数,不会抛出错误,而会继续略过他继续向下传递

let p = new Promise(resolve => resolve(43));
p.then(undefined, 443).then(res => {console.log(res)})
// 43

所以在then方法中需要添加容错处理。

then(onFulfilled, onRejected) {
    // 如果成功回调不是函数则将value继续向下传递,失败则将reason继续向后抛
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
}

then函数中的回调会放入微任务中。在下次eventLoop中执行。

let p = new Promise(resolve => resolve());
// 如果传入不是函数继续向下抛。则会再多执行一次then。需要2次eventLoop
p.then(2323, false).then(res => {console.log(1)});
// 放入微任务 需要1次eventLoop
p.then(res => {console.log(2)});
// 直接执行
console.log(3)

// 3
// 2
// 1

6. 如果resolve或者reject一个promise

如果是resolve(new Promise())的话,就用这个promise的状态作为下个then的状态。
如果是reject(new Promise())的话,不需要执行直接返回。

new Promise(resolve => {
  resolve(new Promise(resovle => resovle(1))) 
}).then((res) => {
  console.log(res);  // 1
})
new Promise((resolve, reject) => {
  reject(new Promise(resovle => resovle(1))) 
}).then(null, (res) => {
  console.log(res);  // Promise {<resolved>: 1}
})

需要在成功的resolve()回调中增加判断

const resolve = value => {
    // 如果resolve()参数传入的是个promise,直接执行它的then方法。
    // 需要等待他执行完毕,拿到他的状态并且返回一个新的promise
    if (value instanceOf Promise) {
        return value.then(resolve, reject);
    }
    if (this.status === PENDING) {
        this.value = value;
        this.status = FULFILLED;
        this.onResolvedCallbacks.forEach(fn => fn());
     }
}

实例:

new Promise(resolve => {
  resolve(new Promise(resovle => resovle(1))) 
}).then(() => {
  console.log("tick 3");
}).then(() => {
  console.log('tick 4')
});

new Promise(resovle => resovle(1)).then(() => {
    console.log("tick 1");
}).then(() => {
    console.log("tick 2");
});
// 1 2 3 4

// 因为resolve()一个promise需要等待他执行完毕,并且then方法返回一个新的promise。
// 相当于如下操作:
new Promise(resolve => resolve(1)).then(res => {
  // 调用resolve中promise的then,返回一个新的promise。
  // 这个promise的resolve值已经是普通值了。如果继续嵌套那就继续递归。
  // 如果then中return promise 还是会等待他执行完then方法的。
  return new Promise(resolve => resolve(res));
}).then((res) => {
  console.log("tick 3",res);
}).then((res) => {console.log('tick 4', 1)});

// 也就相当于多了两次then。
new Promise(resolve => resolve(1)).then(res => res).then(res => res).then(res => {
    console.log('tick 3')
}).then(() => {
  console.log('tick 4')
});

基本架构

所以根据上述promise/A+规范。写出最基本的Promise源码如下。

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
    constructor (executor) {
        this.value = undefined;
        this.reason = undefined;
        this.status = PENDING;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = value => {
            if (value instanceOf Promise) {
                return value.then(resolve, reject);
            }
            if (this.status === PENDING) {
                this.value = value;
                this.status = FULFILLED;
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }
        const reject = reason => {
            if (this.status === PENDING) {
                this.reason = reason;
                this.status = REJECTED;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err};

        if (this.status === FULFILLED) {
            onFulfilled(this.value);
        }

        if (this.status === REJECTED) {
            onRejected(this.reason);
        }

        if (this.status === PENDING) {
            this.onResolvedCallbacks.push(() => {
                onFulfilled(this.value);
            })
            this.onRejectedCallbacks.push(() => {
                onRejected(this.reason);
            })
        }
    }
}

相关文章

网友评论

      本文标题:js 异步系列(2)-promise上

      本文链接:https://www.haomeiwen.com/subject/zizlsctx.html