写一个自己的Promise

195 阅读5分钟

相信Promise已经是现代大部分前端处理异步的方式了。但是Promise天天都在用,它内部到底做了什么呢?那么就按照Promises/A+规范做一个

home

实现最基本的功能

首先的Promise平时都是new出来的,说明它是一个类。那么就先定义一个,名字随意。Promise接受一个参数,这个参数会是一个函数,接受两个参数resolve和reject。resolve为成功,reject为失败。各自也都是一个可以执行的函数。最后不要忘了,还有原型上的then方法。
请看下面这个git commit
初始化

按照规定,Promise会有三个状态 :成功、失败和等待。当在等待的时候可以变成成功或者失败。失败和成功之后不可再改变状态。并且失败会把失败的reason传过去,成功会把成功的value传过去。
先定义成功和失败的默认值以及初始状态
默认的成功值,默认的失败原因,初始状态 (一时手快pending拼写错了。。。尴尬。。)

接下去去完善resolve和reject的功能,他们俩个调用之后首先都是回去判断状态,然后再去改变状态为成功或者失败,以及保存下相应的reason或者value
resolve 和 reject 判断pending,然后改变状态和值

下一步把then写一下,then它主要做了什么?接受两个参数,两个参数都是分别为成功的回调的和失败的回调,然后再通过状态去判断是执行失败的还是成功的。
定义then里面失败或者成功的回调的执行方式


增加处理异步的能力

到这一步代码已经可以跑起来了

let FakePromise = require('./fakePromise')
let p = new FakePromise(function(resolve, reject) {
    resolve(1)
})
p.then(
        (data)=>{ 
            console.log(data)
        },
        (err)=>{
            console.log(err)
    })

但是resolve只要被异步调用了,那么就歇菜了。因为异步的代码会在同步代码执行完之后再执行,所以then调用的时候状态还是在等待状态,既不是成功也不是失败。所以需要对还是等待态做判断,把成功或者失败的回调先保存起来,等resolve或者reject 的时候再去调用。
设置变量存放then 成功和失败的回调
在pending态的时候把成功和失败的回调分别放到他们对应的队列里
当成功或者失败的时候再把对应队列里的函数循环执行
这个时候我们已经可以处理下面这样的情况了

let FakePromise = require('./fakePromise')
let p = new FakePromise((resolve, reject) => {
    setTimeout(() => {
        resolve('setTimeout');
    }, 500);
})
p.then((res) => {
    console.log(res);
}, (err) => {
    console.log(err);
})


对错误捕获的

let FakePromise = require('./fakePromise')
let p = new FakePromise((resolve, reject) => {
   throw new Error('错误')
})
p.then((res) => {
    console.log(res);
}, (err) => {
    console.log(err);
})

接下来处理这种不调用resolve也不reject 直接抛出一个错误的情况
直接throw 一个error时的处理


链式调用

原生的promise是可以进行链式调用的,要实现这个我们需要在成功或者失败的的时候再返回一个promise
调用then之后再返回一个新的promise

但是当链式调用的时候会有两种情况,一种在then里return一个普通值,像这样

let FakePromise = require('./fakePromise')
let p = new FakePromise((resolve, reject) => {
    resolve(100);
})
p.then((res)=>{
    console.log(res); // 100
    return 200 
},(err)=>{
    console.log(err); 
}).then((res)=>{
    console.log(res); // 200   
},(err)=>{
    console.log(err);
})

这种情况我们目前的代码时可以处理的,但是还有直接返回一个promise的,像这样

let FakePromise = require('./fakePromise')
let p = new FakePromise((resolve, reject) => {
    resolve();
})
p.then((res)=>{
    return new Promise((resolve,reject)=>{
        resolve(100); 
    })
},(err)=>{
    console.log(err); 
}).then((res)=>{
    console.log(res); //100    
},(err)=>{
    console.log(err);
})

这样的情况我们还处理不了,所以继续加代码
首先把返回的值存一下
写一个方法来判断和处理这个x
1.处理它是不是自己返回了自己
2. 如果x是普通值就直接resolve掉
3.如果不是null 又是函数或者对象,那么先取一下x里的then,判断一下它是不是方法,如果是调用,如果不是reject。
这里要加一个特殊情况的处理,为了防止别人写的promise库调用了失败或者成功之后再去调用失败或者成功
加一个状态判断


then的穿透

原生的Promise可以这样操作

let FakePromise = require('./fakePromise')
let p = new FakePromise((resolve, reject) => {
    resolve(100);
})
p.then().then().then((data)=>{ 
    console.log(data);//期望拿到100
})

那么我们也实现一下
当then里面默认什么的都没有的时候给它一个默认值
最后promise A+ 规范规定了所有的成功或者失败的操作都需要是异步执行的,所以我们这边用setTimeout创造一个异步的环境,同时因为时异步了,成功或者失败的抛错要try catch一下。
为了符合promise a+ 的标准,所有的成功或者失败都要异步执行


测试是否符合规范

写了这么多,东西到底符合不符合规范?测试一下
添加一个方法供测试用
在当前目录打开命令行

npm install promises-aplus-tests -g
promises-aplus-tests ./你的promise文件

等待全部跑完。。。。。。你会发现有一堆错误。居然翻车了。那么到底哪里写错了?
问题就在这里x在判断的时候应该是不等于null 并且是 object 或者 函数
然后再测一次,基本就成了。到这里核心的功能就都完成了

拓展Promise

Promise.catch
Promise.all
Promise.race
Promise.resolve和Promise.reject
到此为止就把Promise以及它的附加的方法都实现完了