Home > Web Front-end > JS Tutorial > body text

Detailed steps to implement Promise

不言
Release: 2019-03-29 09:13:57
forward
3258 people have browsed it

The content of this article is about the detailed steps to implement Promise. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

1. Constructor

First of all, we all know that Promise has three states. For convenience, we define it as a constant

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
Copy after login

Next we define a class

class MyPromise {
    constructor(executor) {
        //控制状态,使用了一次之后,接下来的都不被使用
        this.state = PENDING;
        this.value = null;
        this.reason = null;
        
        // 定义resolve函数
        const resolve = value => {
            if (this.state === PENDING) {
                this.value = value;
                this.state = FULFILLED;
            }
        }
        
        // 定义reject函数
        const reject = value => {
            if (this.state === PENDING) {
                this.reason = value;
                this.state = REJECTED;
            }
        }
        
        // executor方法可能会抛出异常,需要捕获
        try {
             // 将resolve和reject函数给使用者  
            executor(resolve, reject);
        } catch (error) {
            // 如果在函数中抛出异常则将它注入reject中
            reject(error);
        }
    }
}
Copy after login

This is basically easy to understand. Let me briefly explain it

executor: This is the parameter passed in the constructor when instantiating the Promise object. It is usually a function(resolve,reject){}

state: `The state of Promise, which is the default pending state at the beginning. Whenever the resolve and reject methods are called, its value will be changed. It will be used in the subsequent then` method

value: After the resolve callback is successful, call the parameter value in the resolve method

reason: After the reject callback is successful, call the parameter value in the reject method

resolve: Declare the resolve method in the constructor, Pass it in through the incoming executor method to call back to the user

reject: Declare the reject method in the constructor and pass it in through the incoming executor method to call back to the user

2, then

then is to get the result of resolve or reject in Promise, then we can know that the then method here requires two parameters, a success callback and a failure callback, the code!

then(onFulfilled, onRejected) {
    if (this.state === FULFILLED) {
        onFulfilled(this.value)
    }
    if (this.state === REJECTED) {
        onRejected(this.reason)
    }
}
Copy after login

Let’s simply run the test code

const mp = new MyPromise((resolve, reject)=> {
    resolve('******* i love you *******');
})
mp.then((suc)=> {
console.log(11111, suc);
}, (err)=> {
console.log('****** 你不爱我了*******', err)
})

// 11111 '******* i love you *******'
Copy after login

It seems that there is no problem, so let’s try the asynchronous function?

const mp = new MyPromise((resolve, reject)=> {
    setTimeout(()=> {
        resolve('******* i love you *******');
    }, 0)
})
mp.then((suc)=> {
console.log(11111, suc);
}, (err)=> {
console.log('****** 你不爱我了*******', err)
})
Copy after login

We will find that nothing is printed, what is the problem? It turns out that due to asynchronous reasons, when we execute then, the value of this. state has not changed, so the judgment in then becomes invalid. So how do we solve it?

This is about going back to the classic callback. Come to the source code

// 存放成功回调的函数
this.onFulfilledCallbacks = [];
// 存放失败回调的函数
this.onRejectedCallbacks = [];

const resolve = value => {
    if (this.state === PENDING) {
        this.value = value;
        this.state = FULFILLED;
        this.onFulfilledCallbacks.map(fn => fn());
    }
}

const reject = value => {
    if (this.state === PENDING) {
        this.value = value;
        this.reason = REJECTED;
        this.onRejectedCallbacks.map(fn => fn());
    }
}
Copy after login

Add

then(onFulfilled, onRejected) {
    // ... 
    if(this.state === PENDING) {
        this.onFulfilledCallbacks.push(()=> {
            onFulfilled(this.value);
        });
        this.onRejectedCallbacks.push(()=> {
            onRejected(this.value);
        })
    }
}
Copy after login

in then. Now that the asynchronous problem is solved, let’s execute the test code just now. The results came out. What do we still lack here?

Chain call

What should we do when we don’t pass parameters?

The ideas of these two are also very simple. Chain call means that we Just return an instance of promise. If you don't pass parameters, it's a matter of default values. Let’s take a look at the source code

then(onFulfilled, onRejected) {
    let self = this;
    let promise2 = null;
    //解决onFulfilled,onRejected没有传值的问题
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y
    //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后then的resolve中捕获
    onRejected = typeof onRejected === 'function' ? onRejected : err => {
        throw err;
    }

    promise2 = new MyPromise((resolve, reject) => {
        if (self.state === PENDING) {
            console.log('then PENDING')
            self.onFulfilledCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(self.value);
                    console.log(333333, x, typeof x);
                        self.resolvePromise(promise2, x, resolve, reject);
                    } catch (reason) {
                        reject(reason);
                    }
                }, 0)

            });
            self.onRejectedCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        let x = onRejected(self.reason);
                        self.resolvePromise(promise2, x, resolve, reject);
                    } catch (reason) {
                        reject(reason);
                    }
                }, 0);
            });
        }

        if (self.state === FULFILLED) {
            console.log('then FULFILLED')
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value);
                    self.resolvePromise(promise2, x, resolve, reject);
                } catch (reason) {
                    reject(reason);
                }
            }, 0);
        }

        if (self.state === REJECTED) {
            console.log('then REJECTED')
            setTimeout(() => {
                try {
                    let x = onRejected(self.reason);
                    self.resolvePromise(promise2, x, resolve, reject);
                } catch (reason) {
                    reject(reason);
                }
            })
        }
    });

    return promise2;
}
Copy after login

Why do we need to wrap a layer of setTimeout outside? : Because Promise itself is an asynchronous method and belongs to the microtask category, its value must be obtained after the execution stack is completed, so all return values ​​must include a layer of asynchronous setTimeout.

What is resolvePromise? : This is actually the requirement of official Promise/A. Because your then can return any function, including of course Promise objects, and if it is a Promise object, we need to disassemble it until it is not a Promise object and take the value.

3. resolvePromise

Let’s look at the code directly

resolvePromise(promise2, x, resolve, reject) {
    let self = this;
    let called = false; // called 防止多次调用
    //因为promise2是上一个promise.then后的返回结果,所以如果相同,会导致下面的.then会是同一个promise2,一直都是,没有尽头
    //相当于promise.then之后return了自己,因为then会等待return后的promise,导致自己等待自己,一直处于等待
    if (promise2 === x) {
        return reject(new TypeError('循环引用'));
    }
    //如果x不是null,是对象或者方法
    if (x !== null && (Object.prototype.toString.call(x) === '[object Object]' || Object.prototype.toString.call(x) === '[object Function]')) {
        // x是对象或者函数
        try {
            let then = x.then;

            if (typeof then === 'function') {
                then.call(x, (y) => {
                    // 别人的Promise的then方法可能设置了getter等,使用called防止多次调用then方法
                    if (called) return;
                    called = true;
                    // 成功值y有可能还是promise或者是具有then方法等,再次resolvePromise,直到成功值为基本类型或者非thenable
                    self.resolvePromise(promise2, y, resolve, reject);
                }, (reason) => {
                    if (called) return;
                    called = true;
                    reject(reason);
                });
            } else {
                if (called) return;
                called = true;
                resolve(x);
            }
        } catch (reason) {
            if (called) return;
            called = true;
            reject(reason);
        }
    } else {
        // x是普通值,直接resolve
        resolve(x);
    }
}
Copy after login

Why do we need to judge promise2 and x at the beginning? : First, in Promise/A, it is written that if the two are equal, an exception needs to be thrown. Let me explain why. If the two are equal, we can look at the following example. The first time p2 is p1. The result of then is a Promise object. When this Promise object was created, the resolvePromise(promise2,x,resolve,reject) function was called. And because x is equal to itself and is a Promise, the then method needs to recurse it until it is not Promise object, but the result of x(p2) is still waiting, but he wants to execute his own then method, which will cause waiting.

Why do we need to call the resolvePromise function recursively? : I believe careful people have discovered that I use the recursive calling method here. First of all, this is required in Promise/A. Secondly, it is the need of the business scenario. When we encounter that kind of Promise's resolve, the Promise's resolve also If a Promise is wrapped, the value needs to be retrieved recursively until x is not a Promise object.

4, catch

//catch方法
catch(onRejected){
  return this.then(null,onRejected)
}
Copy after login

5, finally

The finally method is used to specify that no matter what the final state of the Promise object is, it will The operation performed. This method is standard introduced in ES2018.

finally(fn) {
    return this.then(value => {
        fn();
        return value;
    }, reason => {
        fn();
        throw reason;
    });
};
Copy after login

6, resolve/reject

Everyone must have seen the two usages of Promise.resolve() and Promise.reject(). Their functions are actually It just returns a Promise object, let's implement it.

static resolve(val) {
    return new MyPromise((resolve, reject) => {
        resolve(val)
    })
}
//reject方法
static reject(val) {
    return new MyPromise((resolve, reject) => {
        reject(val)
    })
}
Copy after login

7, all

The all method can be said to be a very commonly used method in Promise. Its function is to place an array of Promise objects in it. When The then method will be executed when everything is resolved, and the catch will be executed when there is a reject, and their results are also arranged in the order in the array, so let's implement it.

static all(promiseArr) {
    return new MyPromise((resolve, reject) => {
        let result = [];

        promiseArr.forEach((promise, index) => {
            promise.then((value) => {
                result[index] = value;

                if (result.length === promiseArr.length) {
                    resolve(result);
                }
            }, reject);
        });
    });
}
Copy after login

8、race

race方法虽然不常用,但是在Promise方法中也是一个能用得上的方法,它的作用是将一个Promise数组放入race中,哪个先执行完,race就直接执行完,并从then中取值。我们来实现一下吧。

static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
        promiseArr.forEach(promise => {
            promise.then((value) => {
                resolve(value);
            }, reject);
        });
    });
}
Copy after login

9、deferred

static deferred() {
    let dfd = {};
    dfd.promies = new MyPromise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.rfeject = reject;
    });
    return dfd;
};
Copy after login

什么作用呢?看下面代码你就知道了

let fs = require('fs')
let MyPromise = require('./MyPromise')
//Promise上的语法糖,为了防止嵌套,方便调用
//坏处 错误处理不方便
function read(){
  let defer = MyPromise.defer()
  fs.readFile('./1.txt','utf8',(err,data)=>{
    if(err)defer.reject(err)
    defer.resolve(data)
  })
  return defer.Promise
}
Copy after login

10、测试

const mp1 = MyPromise.resolve(1);
const mp2 = MyPromise.resolve(2);
const mp3 = MyPromise.resolve(3);
const mp4 = MyPromise.reject(4);

MyPromise.all([mp1, mp2, mp3]).then(x => {
    console.log(x);
}, (err) => {
    console.log('err1', err);
})
MyPromise.race([mp1, mp4, mp2, mp3]).then(x => {
    console.log(x);
}, (err) => {
    console.log('err2', err);
})

var mp = new MyPromise((resolve, reject) => {
    console.log(11111);
    setTimeout(() => {
        resolve(22222);
        console.log(3333);
    }, 1000);
});
mp.then(x => {
    console.log(x);
}, (err) => {
    console.log('err2', err);
})
//11111
//[ 1, 2, 3 ]
//1
//3333
//22222
Copy after login

完整源码请查看

本篇文章到这里就已经全部结束了,更多其他精彩内容可以关注PHP中文网的的JavaScript教程视频栏目!!!

The above is the detailed content of Detailed steps to implement Promise. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:segmentfault.com
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template