Maison > interface Web > js tutoriel > le corps du texte

Étapes détaillées pour mettre en œuvre Promise

不言
Libérer: 2019-03-29 09:13:57
avant
3257 Les gens l'ont consulté

Le contenu de cet article concerne les étapes détaillées de mise en œuvre de Promise. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

1. Constructeur

Tout d'abord, nous savons tous que Promise a trois états. Pour plus de commodité, nous la définissons comme une constante

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
Copier après la connexion

Ensuite, nous définissons un. 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);
        }
    }
}
Copier après la connexion

C'est fondamentalement facile à comprendre. Laissez-moi l'expliquer brièvement

exécuteur : Il s'agit du paramètre passé dans le constructeur lors de l'instanciation de l'objet Promise, généralement une fonction(resolve, rejet){ >

state : `L'état de Promise, qui est l'état en attente par défaut au début. Chaque fois que les méthodes de résolution et de rejet sont appelées, sa valeur sera modifiée par la suite. then` méthode

valeur : une fois le rappel de résolution réussi, appelez la valeur du paramètre dans la méthode de résolution

raison : une fois le rappel de rejet réussi, appelez la valeur du paramètre dans la méthode de rejet

🎜>

resolve : déclare que la méthode de résolution est dans le constructeur, elle est transmise via la méthode d'exécution entrante pour rappeler l'utilisateur.

reject : déclare que la méthode de rejet est dans le. constructeur et transmis via la méthode d'exécution entrante à utiliser ou rappel

2, then

then doit obtenir le résultat de la résolution ou du rejet dans Promise, nous pouvons alors savoir que then La méthode ici nécessite deux paramètres, le rappel de réussite et le rappel d'échec, suivez le code !
then(onFulfilled, onRejected) {
    if (this.state === FULFILLED) {
        onFulfilled(this.value)
    }
    if (this.state === REJECTED) {
        onRejected(this.reason)
    }
}
Copier après la connexion

Exécutons simplement le code de test
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 *******'
Copier après la connexion

Il semble qu'il n'y ait pas de problème, alors essayons la fonction asynchrone ?
const mp = new MyPromise((resolve, reject)=> {
    setTimeout(()=> {
        resolve('******* i love you *******');
    }, 0)
})
mp.then((suc)=> {
console.log(11111, suc);
}, (err)=> {
console.log('****** 你不爱我了*******', err)
})
Copier après la connexion

Nous constaterons que rien n'est imprimé, quel est le problème ? Il s'avère que pour des raisons asynchrones, lorsque nous exécutons then, la valeur de cet état n'a pas changé, donc le jugement devient alors invalide. Alors, comment pouvons-nous le résoudre ?

Ce sera un rappel classique. Accédez au code source
// 存放成功回调的函数
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());
    }
}
Copier après la connexion

et ajoutez
then(onFulfilled, onRejected) {
    // ... 
    if(this.state === PENDING) {
        this.onFulfilledCallbacks.push(()=> {
            onFulfilled(this.value);
        });
        this.onRejectedCallbacks.push(()=> {
            onRejected(this.value);
        })
    }
}
Copier après la connexion

dedans. Ok, maintenant que le problème asynchrone est résolu, exécutons le code de test tout de suite. Les résultats sont tombés. Que nous manque-t-il encore ici ? Appel en chaîne

Que faut-il faire lorsque nous ne transmettons pas de paramètres ?

Les idées de ces deux-là sont également très simples, ce qui signifie que nous. Renvoyez simplement une instance de promesse. Si vous ne transmettez pas de paramètres, c'est une question de valeurs par défaut. Jetons un coup d'œil au code source
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;
}
Copier après la connexion

Pourquoi y a-t-il une couche de setTimeout enveloppée à l'extérieur ? : Étant donné que Promise elle-même est une méthode asynchrone et appartient à la série de microtâches, sa valeur doit être obtenue une fois la pile d'exécution terminée, donc toutes les valeurs de retour doivent inclure une couche de setTimeout asynchrone.

Qu'est-ce que solvePromise ? : C'est en fait l'exigence de la promesse officielle/A+. Parce que vous pouvez alors renvoyer n'importe quelle fonction, y compris bien sûr les objets Promise, et s'il s'agit d'un objet Promise, nous devons le démonter jusqu'à ce qu'il ne soit plus un objet Promise et prendre la valeur.

3. solvePromise

Regardons directement le code
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);
    }
}
Copier après la connexion


Pourquoi devons-nous juger promise2 et x au début ? : Premièrement, dans Promise/A+, il est écrit que si les deux sont égaux, une exception doit être levée. Laissez-moi vous expliquer pourquoi si les deux sont égaux, nous pouvons regarder l'exemple suivant. La première fois que p2 est p1. . Le résultat de then est un objet Promise. La fonction solvePromise(promise2,x,resolve,reject) a été appelée lors de la création de cet objet Promise. Et comme x est égal à lui-même et est une promesse, la méthode then doit le récurer. jusqu'à ce que ce ne soit plus un objet Promise, mais que le résultat de x(p2) soit toujours en attente, mais il souhaite exécuter sa propre méthode then, ce qui provoquera une attente.

Pourquoi devons-nous appeler la fonction solvePromise de manière récursive ? : Je crois que des gens prudents ont découvert que j'utilise ici la méthode d'appel récursive. Tout d'abord, cela est requis par Promise/A+. Deuxièmement, c'est la nécessité du scénario commercial. Lorsque nous rencontrons ce genre de résolution de Promise, il y a. la résolution d'une autre promesse. Si une promesse est incluse, la valeur doit être récupérée de manière récursive jusqu'à ce que x ne soit pas un objet Promise.

4. catch

//catch方法
catch(onRejected){
  return this.then(null,onRejected)
}
Copier après la connexion

5 enfin

La méthode enfin est utilisée pour spécifier l'état final de. l'objet Promise, quel que soit, toutes les opérations seront effectuées. Cette méthode est introduite dans la norme dans ES2018.
finally(fn) {
    return this.then(value => {
        fn();
        return value;
    }, reason => {
        fn();
        throw reason;
    });
};
Copier après la connexion

6. résoudre/rejeter

Tout le monde doit avoir vu les deux utilisations de Promise.resolve() et Promise.reject(). en fait, pour renvoyer un objet Promise, implémentons-le.
static resolve(val) {
    return new MyPromise((resolve, reject) => {
        resolve(val)
    })
}
//reject方法
static reject(val) {
    return new MyPromise((resolve, reject) => {
        reject(val)
    })
}
Copier après la connexion

7. all

all peut être considérée comme une méthode très couramment utilisée dans Promise. Sa fonction est de placer un tableau d'objets Promise dans. it. , la méthode then sera exécutée lorsque tout sera résolu, et le catch sera exécuté en cas de rejet, et leurs résultats sont également classés dans l'ordre dans le tableau, implémentons-le donc.
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);
        });
    });
}
Copier après la connexion

8. course

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

static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
        promiseArr.forEach(promise => {
            promise.then((value) => {
                resolve(value);
            }, reject);
        });
    });
}
Copier après la connexion

9、deferred

static deferred() {
    let dfd = {};
    dfd.promies = new MyPromise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.rfeject = reject;
    });
    return dfd;
};
Copier après la connexion

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

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
}
Copier après la connexion

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
Copier après la connexion

完整源码请查看

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:segmentfault.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal