[JavaScript] Рукописное обещание, которое вы должны знать

JavaScript
[JavaScript] Рукописное обещание, которое вы должны знать

написать впереди

PromiseдаES6Базовая грамматика также является наиболее часто задаваемым вопросом на собеседовании, поэтому необходимо понимать правила ее реализации.Умение писать от руки поможет вам выделиться на собеседовании и предпринять шаги,offerТвердый шаг

В предыдущей статье я перевелPromise A+Спецификация, прежде чем читать эту статью, рекомендуется прочитать:【Обещание】Обещания/A+ китайский перевод

Рукописное обещание

Обзор промисов

Promiseэто схема управления асинхронным программированием, это конструктор, который доступен для каждого использованияnewСоздайте экземпляр; он имеет три состояния:pending,fulfilledиrejected, эти три состояния не будут затронуты внешним миром, и состояние может быть определено толькоpendingстатьfullfilled(успех),pendingстатьrejected(неудача), а один раз поменяется, больше не изменится.После изменения состояния вернет успешный результат или причину неудачи, выкинетresolve,reject,catch,finally,then,all,race,done, в последнем предложении добавленоallSettledметод, он будет возвращаться независимо от успеха или неудачи. Далее мы реализуем весьPromise

исполнительная функция

Мы знаем, что при создании экземпляра промиса он будет выполнен немедленно.executorфункция,executorВ функцию передаются два параметра,resolveиreject,еслиexecutorошибка выполнения функции,PromiseСостояние экземпляра меняется наrejected

class MyPromise{
    constructor(executor) {
        this.status = "pending";     // 初始化状态为pending
        this.value = undefined;      // 初始化返回的成功的结果或者失败的原因
        
        // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回
        let resolve = result => {
            if(this.status !== "pending") return;  // 状态一旦改变,就不会再变
            this.status = "resolved";
            this.value = result;
        }
        
        // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回
        let reject = reason => {
            if(this.status !== "pending") return;
            this.status = "rejected";
            this.value = reason;
        }
        // try、catch捕获异常,如果错误,执行reject方法
        try {
            executor(resolve, reject)
        } catch(err) {
            reject(err)
        }
    }
}

Давайте проверим это сейчасPromiseкак это выглядит

let p1 = new MyPromise((resolve, reject) => {
    resolve(1);
})

let p2 = new MyPromise((resolve, reject) => {
    reject(2);
})

console.log(p1);
console.log(p2);

Как видите, состояние изменилось, а значение внутри — это тоже результат успеха и причина неудачи.thenМетод имеет два параметра, первый параметр выполняется в случае успеха, второй параметр выполняется в случае неудачи.thenСвязанный вызов аналогичен массиву и т. д. Он вернетPromiseпример. В случае успеха первыйthenУспешная функция вnull, он будет продолжать смотреть вниз до тех пор, пока не перестанетnullвыполнение функции, предыдущийthenВозвращенный результат напрямую повлияет на следующийthenКакая функция выполняется успешно или нет, разобравшись с этим, давайте попробуем реализовать ее~

затем метод

then(resolveFn, rejectFn) {
    // 如果传入的两个参数不是函数,则直接执行返回结果
    let resolveArr = [];
    let rejectArr = [];
    
    if(typeof resolveFn !== "function") {
        resolveFn = result => {
            return result;
        }
    }
    
    if(typeof rejectFn !== "function") {
        rejectFn = reason => {
            return MyPromise.reject(reason);
        }
    }
    
    return new Mypromise((resolve, reject) => {
        resolveArr.push(result => {
            try {
                let x = resolveFn(result);
                
                if(x instanceof MyPromise) {
                    x.then(resolve, reject)
                    return;
                }
                
                resolve(x);
            } catch(err) {
                reject(err)
            }
        })
        
        rejectArr.push(reason => {
            try {
                let x = rejectFn(reason);
                
                if(x instanceof MyPromise) {
                    x.then(resolve, reject)
                    return;
                }
                
                resolve(x);
            } catch(err) {
                reject(err)
            }
        })
    })
}

Разберем код выше

class MyPromise{
    constructor(executor) {
        this.status = "pending";     // 初始化状态为pending
        this.value = undefined;      // 初始化返回的成功的结果或者失败的原因
        this.resolveArr = [];        // 初始化then中成功的方法
        this.rejectArr = [];         // 初始化then中失败的方法
        
        
        // 定义change方法,因为我们发现好像resolve和reject方法共同的地方还挺多🤔
        let change = (status, value) => {
            if(this.status !== "pending") return;  // 状态一旦改变,就不会再变
            this.status = status;
            this.value = value;
            
            // 根据状态判断要执行成功的方法或失败的方法
            let fnArr = status === "resolved" ? this.resolveArr : this.rejectArr;
            
            // fnArr中的方法依次执行
            fnArr.forEach(item => {
                if(typeof item !== "function") return;
                item(this. value);
            })
        }
        // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回
        let resolve = result => {
            change("resolved", result)
        }
        
        // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回
        let reject = reason => {
            change("rejected", reason);
        }
        
        // try、catch捕获异常,如果错误,执行reject方法
        try {
            executor(resolve, reject)
        } catch(err) {
            reject(err)
        }
    }
    
    then(resolveFn, rejectFn) {
    // 如果传入的两个参数不是函数,则直接执行返回结果
    
        if(typeof resolveFn !== "function") {
            resolveFn = result => {
                return result;
            }
        }
        
        if(typeof rejectFn !== "function") {
            rejectFn = reason => {
                return MyPromise.reject(reason);
            }
        }
        
        return new MyPromise((resolve, reject) => {
            this.resolveArr.push(result => {
                try {
                    let x = resolveFn(result);  // 获取执行成功方法返回的结果
                    
                    // 如果x是一个promise实例,则继续调用then方法 ==> then链的实现
                    if(x instanceof MyPromise) {
                        x.then(resolve, reject)
                        return;
                    }
                    
                    // 不是promise实例,直接执行成功的方法
                    resolve(x);
                } catch(err) {
                    reject(err)
                }
            })
            
            this.rejectArr.push(reason => {
                try {
                    let x = rejectFn(reason);
                    
                    if(x instanceof MyPromise) {
                        x.then(resolve, reject)
                        return;
                    }
                    
                    resolve(x);
                } catch(err) {
                    reject(err)
                }
            })
        })
    }
}

Давайте посмотрим на эффект

new MyPromise((resolve, reject) => {
    resolve(1);
}).then(res => {
    console.log(res, 'success');
}, err => {
    console.log(err, 'error');
})

В это время возникает проблема.Мы обнаруживаем, что вывода вроде бы нет.Что, если мы внесем небольшое изменение в приведенный выше тестовый пример?

new MyPromise((resolve, reject) => {
    setTimeout(_ => {
        resolve(1);
    }, 0)
}).then(res => {
    console.log(res, 'success');    // 1 "success"
}, err => {
    console.log(err, 'error');
})

Это потому, что созданиеPromiseЭкземпляр выполняется немедленноexecutorфункция, еще не выполненнаяthenметод, то массив, независимо от успеха или неудачи, пуст. Тогда может у друзей есть сомнения, зачем ты это добавил?setTimeoutЭто достаточно хорошо? Это связано с тем, что в механизме очереди событийsetTimeoutОн будет помещен в очередь событий и выполнен после завершения основного потока.thenВ методе будет храниться функция успеха или неудачи, так что будь то успешный массив или неудачный массив, в нем уже есть значение, и оно будет полностью выполнено в это время~

Но мы не можем писать при использованииsetTimeoutВ качестве решения, поскольку мы инкапсулируем, мы должны решить проблему в инкапсулированной функции, Согласно этой идее, мы также можемresolveиrejectПри выполнении метода оценивается, есть ли значение в массиве, если нет, то можно использоватьsetTimeoutПусть это задержит выполнение, код выглядит следующим образом~

// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回
let resolve = result => {   
    // 如果数组中有值,则立即改变状态
    if(this.resolveArr.length > 0) {
        change("resolved", result)
    }
    // 如果没值,则延后改变状态
    let timer = setTimeout(_ => {
        change("resolved", result)
        clearTimeout(timer);
    }, 0)
}

// 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回
let reject = reason => {
// 如果数组中有值,则立即改变状态
    if(this.rejectArr.length > 0) {
        change("rejected", reason);
    }
    // 如果没值,则延后改变状态
    let timer = setTimeout(_ => {
        change("rejected", reason);
        clearTimeout(timer);
    }, 0)
}

Теперь попробуем еще раз

// 1、已经成功了
new MyPromise((resolve, reject) => {
    resolve('我成功啦,吼吼吼~~~~');            
    reject('我都已经成功了,你别想让我失败,哼~~');
}).then(res => {
    console.log(res, 'success');         // 我成功啦,吼吼吼~~~~ success
}, err => {
    console.log(err, 'error');
})

// 2、先失败了
new MyPromise((resolve, reject) => {
    reject('失败了,我好委屈,呜呜呜~~');
    resolve('已经失败了~~~');            
}).then(res => {
    console.log(res, 'success');         
}, err => {
    console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error
})

// 3、链式调用
new MyPromise((resolve, reject) => {
    reject('失败了,我好委屈,呜呜呜~~');
    resolve('已经失败了~~~');            
}).then(res => {
    console.log(res);
}, err => {
    console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error
    return '我要发奋图强,不会被困难所击倒,我要成功!!!'
}).then(res1 => {
    console.log(res1, '经过不懈努力,我终于在第二次成功了~');  // 我要发奋图强,不会被困难所击倒,我要成功!!!  经过不懈努力,我终于在第二次成功了~
}, err1 => {
    console.log(err1, '第二次失败');
})

Это отлично решает первый вызов и не будет выполнятьсяthenметодический вопрос. При этом реализованы связанные вызовы. Для связанного вызова я скажу еще несколько слов, На самом деле, является ли это цепным вызовом массива, это потому, что последний возврат - это этот экземпляр.

метод ловли

catchметод состоит в том, чтобы поймать исключение, которое иthenВторая функция обратного вызова метода такая же

catch(rejectFn) {
    return this.then(null, rejectFn)
}

метод решения

мы знаем,Promsieтакже можно использовать так

let p1 = MyPromise.resolve(1);

console.log(p1);

Мы ожидали чего-то подобного, но теперь точно выдает ошибку:MyPromise.resolveне метод

Теперь нам нужно инкапсулироватьresolveметод, мы должны четко понимать, чтоresolveпосле,Promiseзаключается в поддержке непрерывных цепных вызововthen, поэтому нам нужно выполнитьresolveметод, который возвращаетPromiseпример

static resolve(result) {
    // 返回新的promise实例,执行promise实例中resolve方法
    return new MyPromise(resolve => {
        resolve(result)
    })
}

метод отклонения

рисунокresolveМетод тот же, за исключением того, что он получает неисправную функцию

static reject(reason) {
    // 返回新的promise实例,执行promise实例中reject方法
    return new MyPromise((_, reject) => {
        reject(reason);
    })
}

готовый метод

ES6Вводная стандартная книга, даdoneОбъяснение метода следующее:Независимо от того, заканчивается ли цепочка обратного вызова объекта Promise методом then или методом catch, пока последний метод выдает ошибку, она может не быть перехвачена. С этой целью Promise предоставляет метод done, который всегда находится в конце цепочки обратного вызова и гарантированно выдает любые возможные ошибки.. Что ж, мы знаем, что делает этот метод, давайте начнем писать прямо сейчас~

done(resolveFn, rejectFn) {
    this.then(resolveFn, rejectFn)
        .catch(reason => {
            setTimeout(() => {
                throw reason;
            }, 0)
        })
}

он может получитьfulfilled,rejectedФункция обратного вызова состояния или параметры не могут быть предоставлены. Но несмотря ни на что,doneМетод поймает любую возможную ошибку и выдаст ее в глобальную

наконец метод

finallyМетод — это метод, который будет выполняться независимо от успеха или неудачи.Такие методы также есть в апплете.completeметод и т.д., давайте попробуем его реализовать~

finally(finallyFn) {
    let P = this.constructor;
    return this.then(
        value => P.resolve(finallyFn()).then(() => value),
        reason => P.reject(finallyFn()).then(() => reason)
    )
}

Давайте проверим

new MyPromise((resolve, reject) => {
    reject('失败了,我好委屈,呜呜呜~~');
    resolve('已经失败了~~~');
}).then(res => {
    console.log(res);
}, err => {
    console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error
    return '我要发奋图强,不会被困难所击倒,我要成功!!!'
}).finally(() => {
    console.log('执行了吗');            // 这里会输出"执行了吗"
})

все методы

allМетод получает массив и возвращается только тогда, когда каждый экземпляр в массиве успешен, и возвращает массив, и каждый параметр является соответствующимpromiseРезультат возвращается, если один терпит неудачу,allметод возвращает ошибку

// 接收数组参数
static all(promiseList) {
    // 返回新实例,调用后还可使用then、catch等方法
    return new MyPromise((resolve, reject) => {
        let index = 0,      // 成功次数计数
            results = [];   // 返回的结果
        
        for(let i = 0; i < promiseList.length; i++) {
            let item = promiseList[i];
            
            // 如果item不是promise实例
            if(!(item instanceof MyPromise)) return;
            
            item.then(result => {
                index++;
                results[i] = result;
                if(index === promiseList.length) {
                    resolve(results);
                }
            }).catch(reason => {
                reject(reason);
            })
        }
    })
}

проверять

// 1.有失败的情况
let p1 = MyPromise.resolve(1);

let p2 = MyPromise.reject(2);

let p3 = MyPromise.resolve(3);

MyPromise.all([p1, p2, p3])
    .then(res => {
        console.log(res);
    }).catch(err => {
        console.log(err, 'err');     // 2 "err"
    })

// 2.无失败的情况
let p1 = MyPromise.resolve(1);

let p2 = MyPromise.resolve(2);

let p3 = MyPromise.resolve(3);

MyPromise.all([p1, p2, p3])
    .then(res => {
        console.log(res, 'success');   // [1, 2, 3] "success"
    }).catch(err => {
        console.log(err, 'err');
    })

метод гонки

raceМетод также получает параметр массива, каждый из которыхPromiseэкземпляр, который возвращает самое быстрое изменение состоянияPromiseрезультат метода экземпляра

static race(promiseList) {
    return new MyPromise((resolve, reject) => {
        promiseList.forEach(item => {
            if(!(item instanceof MyPromise)) return;
            
            item.then(result => {
                resolve(result);
            }).catch(err => {
                reject(err)
            })
        })
    })
}

проверять

// 1.
let p1 = MyPromise.resolve(1);

let p2 = MyPromise.reject(2);

let p3 = MyPromise.resolve(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res);            // 1 'success'
    }).catch(err => {
        console.log(err, 'err');    
    })

// 2.
let p1 = MyPromise.reject(1);

let p2 = MyPromise.resolve(2);

let p3 = MyPromise.resolve(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, 'success');   
    }).catch(err => {
        console.log(err, 'err');       // 1 'err'
    })
    
// 3.
let p1 = MyPromise.reject(1);

let p2 = MyPromise.reject(2);

let p3 = MyPromise.reject(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, 'success');   
    }).catch(err => {
        console.log(err, 'err');       // 1 'err'
    })

попробуйте реализовать метод allSettled

allSettledМетод также принимает параметры массива, но возвращает результат в случае успеха или неудачи.

static allSettled(promiseList) {
    return new MyPromise((resolve, reject) => {
        let results = [];
        
        for(let i = 0; i < promiseList.length; i++) {
            let item = promiseList[i];
            
            if(!(item instanceof MyPromise)) return;
            
            item.then(result => {
                results[i] = result;
            }, reason => {
                results[i] = reason;
            })
            resolve(results);
        }
    })
}

проверять

// 1.
let p1 = MyPromise.resolve(1);

let p2 = MyPromise.resolve(2);

let p3 = MyPromise.resolve(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res);            // [1, 2, 3] 'success'
    }).catch(err => {
        console.log(err, 'err');    
    })

// 2.
let p1 = MyPromise.reject(1);

let p2 = MyPromise.reject(2);

let p3 = MyPromise.reject(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, 'success');   // [1, 2, 3] 'success'
    }).catch(err => {
        console.log(err, 'err');       
    })
    
// 3.
let p1 = MyPromise.resolve(1);

let p2 = MyPromise.reject(2);

let p3 = MyPromise.resolve(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, 'success');   // [1, 2, 3] 'success'
    }).catch(err => {
        console.log(err, 'err');       
    })

Наконец

В этой статье реализован один из собственныхPromise, исходный код был загружен наgithub:Исходный адрес MyPromise. Друзья, которым это нужно, могут получить это сами ~

Вы также можете следить за моей официальной учетной записью «веб-дневник», чтобы получать push-сообщения более своевременно ~