Реализация обещаний, которые должны знать фронтенд-инженеры

внешний интерфейс JavaScript Promise

Что такое обещания?

В мире Javascript код выполняется в одном потоке. Это приводит к вложенным функциям обратного вызова в нашем коде.Если вложенности слишком много, код становится непростым для понимания и сопровождения.

Чтобы уменьшить сложность асинхронного программирования, разработчики искали решения, Promise — лишь один из вариантов обработки асинхронных операций.

Ниже я буду комбинировать сценарии использования Promise, чтобы последовательно написать принцип реализации Promise.

Поскольку ES6 наконец приняла спецификацию Promise/A+, весь следующий код реализован на основе спецификации Promise/A+. Заинтересованные студенты могут просмотретьСпецификация Promise/A+ https://promisesaplus.com/


Основное использование промисов:

let promise = new Promise((resolve,reject)=>{
  resolve('success'); //这里如果是reject('fail')
});
promise.then((res)=>{
  console.log(res); // 输出:success
},(err)=>{
  console.log(err); // 上面如果执行reject('fail') 这里就输出:fail
});

Это легко понять, взглянув на картинку: resolve находит успешный обратный вызов в then, а reject находит неудачный обратный вызов в then.

Так как же реализовать такой класс Promise по-своему шаг за шагом? Перейдем непосредственно к коду, я прокомментирую чуть подробнее.

class Promise { //创建一个Promise类
    constructor(executor) {
        this.status = 'pending'; //初始默认状态为pending
        this.value = undefined; //默认赋值为undefined
        this.reason = undefined; //默认赋值为undefined
        let resolve = (value) => {
            if (this.status === 'pending') { //只有状态为pending才能转换状态
                this.value = value; //将传递进来的的值赋给value保存
                this.status = 'resolved'; //将状态设置成resolved
            }
        }
        let reject = (reason) => {
            if (this.status === 'pending') { //只有状态为pending才能转换状态
                this.reason = reason; //将传递进来的失败原因赋给reason保存
                this.status = 'rejected'; //将状态设置成rejected
            }
        }
        executor(resolve, reject); //默认执行executor
    }
    then(onFulfilled, onRejected) { //等同于es5的Promise.prototype.then 当调用then的时候,根据状态,来执行不同的函数
        if (this.status === 'resolved') { //如果状态是resolved
            onFulfilled(this.value); //执行成功的resolve,并将成功后的值传递过去
        }
        if (this.status === 'rejected') { //如果状态是rejected
            onRejected(this.reason); //执行失败的reject,并将失败原因传递过去
        }
    }
}
module.exports = Promise; //将Promise导出

Теперь, когда библиотека Promise введена в код js, можно реализовать самые основные функции Promise.

Выходной результат: успех


Еще один момент, который следует отметить, это то, что когда код дает сбой, мы должны иметь возможность поймать ошибку и обработать ее. Таким образом, строку кода в executor(resolve, reject) необходимо обработать и обернуть слоем try/catch следующим образом:

try {
    executor(resolve, reject); 
} catch (e) {
    reject(e); //如果发生错误,将错误放入reject中
}

проблема с вызовом setTimeout

В это время класс Promise может вызвать разрешение или отклонение. После вызова затем будет выполнена соответствующая функция. Вроде нет проблем. Но что, если поставить решимость на таймер? Взгляните на код ниже:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('setTimeout');
    }, 500);
})
promise.then((res) => {
    console.log(res);
}, (err) => {
    console.log(err);
})

Мы обнаружим, что приведенный выше код полностью не отвечает в консоли, почему это так? На самом деле это потому, что мы вообще не занимались этой ситуацией с задержкой вызова. Теперь займемся следующим:

class Promise { //创建一个Promise类
    constructor(executor) {
        ...此处略去部分代码
        this.successStore = []; //定义一个存放成功函数的数组
        this.failStore = []; //定义一个存放失败函数的数组
        let resolve = (value) => {
            if (this.status === 'pending') { //只有状态为pending才能转换状态
                ...此处略去部分代码
                this.successStore.forEach(fnc => fnc()); //一次执行数组中的成功函数
            }
        }
        let reject = (reason) => {
            if (this.status === 'pending') { //只有状态为pending才能转换状态
                ...此处略去部分代码
                this.failStore.forEach(fnc => fnc()) //依次执行数组中的失败函数
            }
        }
       ...此处略去部分代码

    }
    then(onFulfilled, onRejected) { //等同于es5的Promise.prototype.then 当调用then的时候,根据状态,来执行不同的函数
        ...此处略去部分代码
        if (this.status === 'pending') { //此处增加一种状态判断
            this.successStore.push(() => { //当状态为pending时将成功的函数存放到数组里
                onFulfilled(this.value);
            })
            this.failStore.push(() => { //当状态为pending时将失败的函数存放到数组中
                onRejected(this.reason);
            })
        }
    }
}
module.exports = Promise; //将Promise导出

Это решает проблему вызова setTimeout.


Цепочка вызовов затем

Внимательные студенты должны были заметить, что в приведенном выше коде цепочка вызовов then не обрабатывается. Если в это время используется связанный вызов метода then(), будет сообщено об ошибке: TypeError: Невозможно прочитать свойство «тогда» неопределенного.

Спецификация Promise/A+ говорит, что метод then() должен возвращать экземпляр Promise, и здесь мы можем понять, что он должен возвращать новый экземпляр Promise. Таким образом, метод then() можно продолжать связывать с другим методом then().

Но метод then() может воспроизводить значение или возвращать экземпляр Promise, что требует от нас отдельного рассмотрения этих двух случаев.

Первый:

let promise = new Promise((resolve, reject) => {
    resolve('success');
})
p.then((res)=>{
    console.log(res); // success
    return 'hello world!' 
},(err)=>{
    console.log(err); 
}).then((res)=>{
    console.log(res); // hello world   
},(err)=>{
    console.log(err);
})

В обратном вызове метода then() возвращается нормальное значение.Будь то успешный или неудачный обратный вызов, он войдет в состояние успеха следующего then(). следующим образом:

let promise = new Promise((resolve, reject) => {
    reject('fail');
})
p.then((res)=>{
    console.log(res); 
},(err)=>{
    console.log(err);  //fail
    return 'fail';
}).then((res)=>{
    console.log(res);  //fail 注意:此时走的是成功回调,并非失败的回调
},(err)=>{
    console.log(err); 
})

Второй:

let promise = new Promise((resolve, reject) => {
    resolve();
})
promise.then((res)=>{
    return new Promise((resolve,reject)=>{ //返回一个新的Promise
        resolve('hello world'); 
    })
},(err)=>{
    console.log(err); 
}).then((res)=>{
    console.log(res); //hello world    
},(err)=>{
    console.log(err);
})

Оба эти случая необходимо учитывать, давайте изменим код ниже:

function handlePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { //promise2是否等于x,也就是判断是否将自己本身返回
        return reject(new TypeError('circular reference')); //如果是抛出错误
    }
    //判断x不是bull且x是对象或者函数
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        let called; //called控制resolve或reject 只执行一次,多次调用没有任何作用。
        try {
            let then = x.then; //取x.then()方法
            if (typeof then === 'function') { //如果是函数,就认为它是返回新的promise
                then.call(x, y => {  //如果y是promise继续递归解析
                    if (called) return;
                    called = true;
                    handlePromise(promise2, y, resolve, reject); //递归解析promise
                }, r => {
                    if (called) return;
                    called = true;
                    reject(r)
                })
            } else { //不是函数,就是普通对象
                resolve(x); //直接将对象返回
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else { //x是普通值,直接走then的成功回调
        resolve(x);
    }
}
class Promise {
    constructor(executor) {
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.successStore = [];
        this.failStore = [];
        let resolve = (value) => {
            if (this.status === 'pending') {
                this.value = value;
                this.status = 'resolved';
                this.successStore.forEach(fn => fn());
            }
        }
        let reject = (reason) => {
            if (this.status === 'pending') {
                this.reason = reason;
                this.status = 'rejected';
                this.failStore.forEach(fn => fn());
            }
        }
        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }
    then(onFulfilled, onRejected) { //原型上的方法
        let promise2; // 返回的新的promise
        if (this.status === 'resolved') {
            promise2 = new Promise((resolve, reject) => {
                try {
                    let x = onFulfilled(this.value);
                    handlePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }

            })
        }
        if (this.status === 'rejected') {
            promise2 = new Promise((resolve, reject) => {
                try {
                    let x = onRejected(this.reason); //x存放返回的结果
                    handlePromise(promise2, x, resolve, reject); //处理返回结果的函数,已经在上面定义
                } catch (e) {
                    reject(e);//报错执行reject
                }
            })
        }
        if (this.status === 'pending') {
            promise2 = new Promise((resolve, reject) => {
                this.successStore.push(() => {
                    try {
                        let x = onFulfilled(this.value); //x存放返回的结果
                        handlePromise(promise2, x, resolve, reject);//处理返回结果的函数,已经在上面定义
                    } catch (e) {
                        reject(e); //报错执行reject
                    }

                })
                this.failStore.push(() => {
                    try {
                        let x = onRejected(this.reason);//x存放返回的结果
                        handlePromise(promise2, x, resolve, reject);//处理返回结果的函数,已经在上面定义
                    } catch (e) {
                        reject(e);//报错执行reject
                    }
                })
            })
        }
        return promise2; //返回新的promise
    }
}
module.exports = Promise;

Проникновение ценности процесса

Сначала посмотрите на пример

let promise = new Promise((resolve, reject)=>{
    resolve('hello world');
})
promise.then().then().then((res)=>{ 
    console.log(res);//我们希望可以正常打印出hello world,如何处理呢?
})

Просто добавьте уровень суждения к методу прототипа then, чтобы определить, передается ли функция then.Если она не передана, мы вручную передаем функцию и позволяем возвращать значение.

then(onFulfilled, onRejected) { //原型上的方法
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y; //判断是否是一个函数
    onRejected = typeof onRejected === 'function' ? onRejected : errr => { //判断是否是一个函数
        throw err; //注意,这里不是返回值,而是抛出错误
    }
}

Таким образом, когда мы вызываем Promise, hello world может быть напечатан нормально.

добавить метод отлова

Далее мы продолжаем добавлять метод catch

then(onFulfilled, onRejected){
    ...
}
catch(onRejected){ //在此处添加原型上的方法catch
    return this.then(null,onRejected);
}

catch() на самом деле является упрощенной версией метода then().У него нет успешных обратных вызовов, только неудачные обратные вызовы. Таким образом, это может быть обработано, как указано выше.

Добавьте метод Promise.all

Promise.all = function (promiseArrs) { //在Promise类上添加一个all方法,接受一个传进来的promise数组
    return new Promise((resolve, reject) => { //返回一个新的Promise
        let arr = []; //定义一个空数组存放结果
        let i = 0;
        function handleData(index, data) { //处理数据函数
            arr[index] = data;
            i++;
            if (i === promiseArrs.length) { //当i等于传递的数组的长度时 
                resolve(arr); //执行resolve,并将结果放入
            }
        }
        for (let i = 0; i < promiseArrs.length; i++) { //循环遍历数组
            promiseArrs[i].then((data) => {
                handleData(i, data); //将结果和索引传入handleData函数
            }, reject)
        }
    })
}

Ниже мы добавляем методы реагирования, разрешения и отклонения один за другим.

Добавьте метод Promise.race

Смысл соревнования по бегу в том, что кто быстро бежит, тот и вернется к кому угодно, независимо от того, добьетесь ли вы успеха или потерпите неудачу. Аналогично методу all, но проще. сразу после цикла

Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve, reject);
        }
    })
}

После приведенного выше кода следующие два метода очень просты, возвращают новое обещание напрямую, а затем выполняют успех/неудачу.

Добавьте метод Promise.resolve

Promise.resolve = function (val) {
    return new Promise((resolve, reject) => resolve(val));
}

Добавьте метод Promise.reject

Promise.reject = function (val) {
    return new Promise((resolve, reject) => reject(val));
}

Если вы хотите использовать promises-aplus-tests для проверки соответствия спецификации Promise/A+, вам нужно добавить фрагмент кода:

Promise.deferred = Promise.defer = function () { //这是promise的语法糖
  let dfd = {};
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  })
  return dfd;
}

После установки promises-aplus-tests в терминале выполните promises-aplus-tests «имя файла» под терминалом файла.

npm install promises-aplus-tests -g
promises-aplus-tests filename

Наконец, для решения асинхронной проблемы полный код выглядит следующим образом:

function handlePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('circular reference'));
    }
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        let called;
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    handlePromise(promise2, y, resolve, reject);
                }, r => {
                    if (called) return;
                    called = true;
                    reject(r)
                })
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }

    } else {
        resolve(x);
    }
}
class Promise {
    constructor(executor) {
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.successStore = [];
        this.failStore = [];
        let resolve = (value) => {
            if (this.status === 'pending') {
                this.value = value;
                this.status = 'resolved';
                this.successStore.forEach(fn => fn());
            }
        }
        let reject = (reason) => {
            if (this.status === 'pending') {
                this.reason = reason;
                this.status = 'rejected';
                this.failStore.forEach(fn => fn());
            }
        }
        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y;
        onRejected = typeof onRejected === 'function' ? onRejected : errr => {
            throw err;
        }
        let promise2; 
        if (this.status === 'resolved') {
            promise2 = new Promise((resolve, reject) => {
                setTimeout(() => { //异步处理
                    try {
                        let x = onFulfilled(this.value);
                        handlePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            })
        }
        if (this.status === 'rejected') {
            promise2 = new Promise((resolve, reject) => {
                setTimeout(() => { //异步处理
                    try {
                        let x = onRejected(this.reason);
                        handlePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            })
        }
        if (this.status === 'pending') {
            promise2 = new Promise((resolve, reject) => {
                this.successStore.push(() => {
                    setTimeout(() => { //异步处理
                        try {
                            let x = onFulfilled(this.value);
                            handlePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                })
                this.failStore.push(() => {
                    setTimeout(() => { //异步处理
                        try {
                            let x = onRejected(this.reason);
                            handlePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                })
            })
        }
        return promise2;
    }
}

Promise.all = function (promiseArrs) {
    return new Promise((resolve, reject) => {
        let arr = [];
        let i = 0;

        function processData(index, data) {
            arr[index] = data;
            i++;
            if (i === promiseArrs.length) {
                resolve(arr);
            }
        }
        for (let i = 0; i < promiseArrs.length; i++) {
            promiseArrs[i].then((data) => {
                processData(i, data);
            }, reject)
        }
    })
}

Promise.deferred = Promise.defer = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}

module.exports = Promise;

Итак, класс Promise, соответствующий спецификации Promise/A+, завершен. Мнения ограничены, если есть неточности в описании, помогите вовремя указать. Если есть какая-то ошибка, она будет исправлена ​​вовремя.

Ссылаться на

сегмент fault.com/ah/119000000…
promisesaplus.com/
zhuanlan.zhihu.com/p/25178630