Какой? Рукописное обещание, когда пьешь Куолуо и ешь арбуз? ? ?

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

предисловие

Несмотря на то, что в этом году исполнилось 18 лет, мы все еще должны говорить о ES6 сегодня, ES6 прошло уже несколько лет, но много ли мы знаем о грамматике ES6? буду использовать? Или опытный? Я считаю, что у всех, как и у меня, есть сердце к самосовершенствованию.Новые игрушки нельзя просто понять, но мысли в них самые привлекательные, поэтому я передам статью, чтобы все узнали о новых игрушках.PromiseЭта игрушка профессиональна! ! !

Откройте бутылку Ice Kuoluo здесь~~~

Promise

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

икота~~~~~~

Во-первых, мы видим буквальноPormiseесть одно решение, а есть два традиционных решения回调函数а также事件, хорошо, тогда давайте сначала поговорим об этих двух вариантах.

Функция обратного вызова

Функция обратного вызова должна быть знакома всем, то есть мы обычно передаем функцию в качестве параметра другой функции, а затем выполняем обратный вызов после выполнения определенных условий.Например, мы хотим реализовать функцию, которая вычисляет от 1 до 5 после три секунды. , затем:

    // 求和函数
    function sum () {
        return eval([...arguments].join('+'))
    }
    // 三秒后执行函数
    function asyncGetSum (callback) {
        setTimeout(function(){
            var result = callback(1,2,3,4,5);
            console.log(result)
        },3000)
    }
    asyncGetSum(sum);

Такая реализация является функцией обратного вызова, но если я хочу реализовать анимацию, процесс выполнения анимации заключается в том, что мяч перемещается на 100 пикселей вправо, затем на 100 пикселей вниз и на 100 пикселей влево, а продолжительность каждой анимации составляет 3 с.

    dom.animate({left:'100px'},3000,'linear',function(){
        dom.animate({top:'100px'},3000,'linear',function(){
            dom.animate({left:'0px'},3000,'linear',function(){
                console.log('动画 done')
            })
        })
    })

Таким образом, вы увидите, что формируется вложение обратного вызова, о чем мы часто говорим回调地狱, что приводит к очень плохой читаемости кода.

мероприятие

обработка событийjQueryсерединаonсвязывать события иtriggerИнициирование событий — это вообще-то наша общая модель публикации-подписки.Когда я подпишусь на событие, то я подписчик.Если издатель опубликует данные, то я получу соответствующее уведомление.

    // 定义一个发布中心
    let publishCenter = {
        subscribeArrays:{}, // 定义一个订阅者回调函数callback
        subscribe:function(key,callback){
            // 增加订阅者
            if(!this.subscribeArrays[key]){
                this.subscribeArrays[key] = [];
            }
            this.subscribeArrays[key].push(callback)
        },
        publish:function(){
            //发布 第一个参数是key
            let params = [...arguments];
            let key = params.shift();
            let callbacks = this.subscribeArrays[key];
            if(!callbacks || callbacks.length === 0){
                // 如果没人订阅 那么就返回
                return false
            }
            for( let i = 0 ; i < callbacks.length; i++ ){
                callbacks[i].apply( this, params );
            }
        }
    };
    
    // 订阅 一个wantWatermelon事件
    publishCenter.subscribe('wantWatermelon',function(){console.log('恰西瓜咯~~')})
    
    //触发wantWatermelon事件 好咯 可以看到 恰西瓜咯
    publishCenter.publish('wantWatermelon')

Просто в арбузе~~~

Promise A+

Hiccup~ok, после еды, давайте перейдем к делу. Видя, что вышеприведенное асинхронное программирование настолько проблематично, для такого крупного пользователя, как я, конечно, оно отвергается. К счастью, у нас естьPormise(PormiseДафа несёт добро), давайте реализуемPromiseполучить более глубокое пониманиеPromiseПринцип, сначала мы понимаемPromiseA+, это спецификация, используемая для того, чтобы заставить всех писатьPromiseспособ, чтобы все могли писатьPromiseУстраните некоторые ошибки и следуйте процессу, который мы ожидаем, чтобыPromiseA+Технические характеристики.

Особенности обещания

Мы основаны наPromiseA+Документация, чтобы увидеть шаг за шагомPromiseКаковы характеристики.

Прежде всего, давайте посмотрим на раздел 2.1 документа, заголовок которого гласит «Обещание», то естьPromiseсостояние, так что вы сказали? Давайте посмотрим:

  • Обещание имеет только три состояния: ожидающее состояние, выполненное состояние (завершенное состояние) и отклоненное (отклоненное состояние).
  • Когда обещание находится в состоянии ожидания, оно может быть преобразовано в выполненное или отклоненное.
  • Как только состояние обещания изменено на выполненное, состояние не может быть изменено, и необходимо предоставить неизменяемое значение.
  • Как только состояние промиса изменяется на отклоненное, состояние не может быть изменено, и необходимо указать неизменную причину.

хорошо, тогда мы начнем писать свой собственныйPromise, давайте посмотрим на обычныйPromiseнаписание

    // 成功或者失败是需要提供一个value或者reason
    let promise1 = new Promise((resolve,rejected)=>{
        // 可以发现 当我们new Promise的时候这句话是同步执行的 也就是说当我们初始化一个promise的时候 内部的回调函数(通常我们叫做执行器executor)会立即执行
        console.log('hahahha');
        // promise内部支持异步
        setTimeout(function(){
            resolve(123);
        },100)
        // throw new Error('error') 我们也可以在执行器内部直接抛出一个错误 这时promise会直接变成rejected态
    })
    

Согласно приведенному выше коду и описанию состояния в спецификации PromiseA+ мы можем узнатьPromiseИмеет следующие особенности

  1. promiseПо умолчанию есть три состоянияpendingсостояниеpendingможет статьfulfilled(состояние успеха) илиrejected(состояние отказа), и после преобразования его нельзя изменить на какое-либо другое значение.
  2. promiseесть один внутриvalueИспользуется для хранения результата успешного состояния
  3. promiseесть один внутриreasonИспользуется для хранения причины состояния отказа
  4. promiseпринять одинexecutorфункция, эта функция имеет два параметра, одинresolveметод, одинrejectметод при выполненииresolveчас,promiseстатус изменился наfulfilled,воплощать в жизньrejectчас,promiseстатус изменился наrejected
  5. По умолчаниюnew Promiseвнутренний во время выполненияexecutorвыполнение функции
  6. promiseВнутренняя поддержка асинхронного изменения состояния
  7. promiseВнутренняя поддержка создания исключений, затемpromiseСтатус напрямую меняется наrejected

Продолжим рассмотрение документа PromiseA+:

  • promiseдолжен иметь одинthenметод для доступа к его текущемуvalueилиreason
  • Метод принимает два параметраonFulfilled(успешно вернул функцию),onRejected(функция обратного вызова с ошибкой)promise.then(onFulfilled, onRejected)
  • Эти два параметра являются необязательными параметрами, если обнаруживается, что эти два параметра не являются типами функций, то они игнорируются.Напримерpromise.then().then(data=>console.log(data),err=>console.log(err))может сформировать ценностное проникновение
  • onFulfilledДолжен бытьpromiseстатус изменился наfulfilledПотом поменять на звонок, и что?promiseВнутреннийvalueЗначение является параметром этой функции, и эту функцию нельзя вызывать повторно.
  • onRejectedДолжен бытьpromiseстатус изменился наrejectedПотом поменять на звонок, и что?promiseВнутреннийreasonЗначение является параметром этой функции, и эту функцию нельзя вызывать повторно.
  • onFulfilledа такжеonRejectedЭти два метода должны быть вызваны после выполнения контекста текущего стека выполнения, который фактически является микрозадачей в цикле обработки событий (setTimeoutэто макрозадача, есть определенные отличия)
  • onFulfilledа такжеonRejectedЭти два метода должны вызываться через функцию, то есть они не вызываются черезthis.onFulfilled()илиthis.onRejected()Звоните, напрямуюonFulfilled()илиonRejected()
  • thenметод может быть вpromiseНесколько вызовов предыдущего, который является нашим общим цепным вызовом
  • если текущийpromiseстатус изменился наfulfilledЗатем выполните его по порядкуthenв методеonFulfilledПерезвоните
  • если текущийpromiseстатус изменился наrejectedЗатем выполните его по порядкуthenв методеonRejectedПерезвоните
  • thenметод должен возвращатьpromise(Далее мы поместим этоpromiseназываетсяpromise2), похожий наpromise2 = promise1.then(onFulfilled, onRejected);
  • что еслиonFulfilled()илиonRejected()либо возвращает значениеx, затем выполнитеresolvePromiseв эту функцию (эта функция используется для обработки возвращаемого значенияxРазличные значения встречаются, а затем на основе этих значений, чтобы решить, что мы только чтоthenметодonFulfilled()илиonRejected()Два обратных вызова возвращаютсяpromise2положение дел)
  • если мы былиthenвыполнить вonFulfilled()илиonRejected()метод, возникает исключение, тоpromise2использовать причину исключенияeидтиreject
  • еслиonFulfilledилиonRejectedне является функцией иpromiseстатус изменился наfulfilledилиrejected, затем используйте тот жеvalueилиreasonобновитьpromise2(Вообще-то этот и третий пункт одинаковые, то есть стоит вникнуть в проблему)

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

    /**
     * 实现一个PromiseA+
     * @description 实现一个简要的promise
     * @param {Function} executor 执行器
     * @author Leslie
     */
    function Promise(executor){
        let self = this;
        self.status = 'pending'; // 存储promise状态 pending fulfilled rejected.
        self.value = undefined; // 存储成功后的值
        self.reason = undefined; // 记录失败的原因
        self.onfulfilledCallbacks = []; //  异步时候收集成功回调
        self.onrejectedCallbacks = []; //  异步时候收集失败回调
        function resolve(value){
            if(self.status === 'pending'){
                self.status = 'fulfilled';// resolve的时候改变promise的状态
                self.value = value;//修改成功的值
                // 异步执行后 调用resolve 再把存储的then中的成功回调函数执行一遍
                self.onfulfilledCallbacks.forEach(element => {
                    element()
                });
            }
        }
        function reject(reason){
            if(self.status === 'pending'){
                self.status = 'rejected';// reject的时候改变promise的状态
                self.reason = reason; // 修改失败的原因
                // 异步执行后 调用reject 再把存储的then中的失败回调函数执行一遍
                self.onrejectedCallbacks.forEach(element => {
                    element()
                });
            }
        }
        // 如果执行器中抛出异常 那么就把promise的状态用这个异常reject掉
        try {
            //执行 执行器
            executor(resolve,reject);
        } catch (error) {
            reject(error)
        }
    }

    Promise.prototype.then = function(onfulfilled,onrejected){
        // onfulfilled then方法中的成功回调
        // onrejected then方法中的失败回调
        let self = this;
        // 如果onfulfilled不是函数 那么就用默认的函数替代 以便达到值穿透
        onfulfilled = typeof onfulfilled === 'function'?onfulfilled:val=>val;
        // 如果onrejected不是函数 那么就用默认的函数替代 以便达到值穿透
        onrejected = typeof onrejected === 'function'?onrejected: err=>{throw err}
        let promise2 = new Promise((resolve,reject)=>{
            if(self.status === 'fulfilled'){
                // 加入setTimeout 模拟异步
                // 如果调用then的时候promise 的状态已经变成了fulfilled 那么就调用成功回调 并且传递参数为 成功的value
                setTimeout(function(){
                    // 如果执行回调发生了异常 那么就用这个异常作为promise2的失败原因
                    try {
                        // x 是执行成功回调的结果
                        let x = onfulfilled(self.value);
                        // 调用resolvePromise函数 根据x的值 来决定promise2的状态
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (error) {
                        reject(error)
                    }
                },0)
                
            }
        
            if(self.status === 'rejected'){
                // 加入setTimeout 模拟异步
                // 如果调用then的时候promise 的状态已经变成了rejected 那么就调用失败回调 并且传递参数为 失败的reason
                setTimeout(function(){
                    // 如果执行回调发生了异常 那么就用这个异常作为promise2的失败原因
                    try {
                        // x 是执行失败回调的结果
                        let x = onrejected(self.reason);
                         // 调用resolvePromise函数 根据x的值 来决定promise2的状态
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (error) {
                        reject(error)
                    }
                    
                },0)
            }
        
            if(self.status === 'pending'){
                //如果调用then的时候promise的状态还是pending,说明promsie执行器内部的resolve或者reject是异步执行的,那么就需要先把then方法中的成功回调和失败回调存储袭来,等待promise的状态改成fulfilled或者rejected时候再按顺序执行相关回调
                self.onfulfilledCallbacks.push(()=>{
                    //setTimeout模拟异步
                    setTimeout(function(){
                        // 如果执行回调发生了异常 那么就用这个异常作为promise2的失败原因
                        try {
                             // x 是执行成功回调的结果
                            let x = onfulfilled(self.value)
                            // 调用resolvePromise函数 根据x的值 来决定promise2的状态
                            resolvePromise(promise2,x,resolve,reject);
                        } catch (error) {
                            reject(error)
                        }
                    },0)
                })
                self.onrejectedCallbacks.push(()=>{
                    //setTimeout模拟异步
                    setTimeout(function(){
                        // 如果执行回调发生了异常 那么就用这个异常作为promise2的失败原因
                        try {
                             // x 是执行失败回调的结果
                            let x = onrejected(self.reason)
                             // 调用resolvePromise函数 根据x的值 来决定promise2的状态
                            resolvePromise(promise2,x,resolve,reject);
                        } catch (error) {
                            reject(error)
                        }
                    },0)
                })
            }
        })
        return promise2;
    }

Считаете ли вы, что описанные выше функции очень эффективны, и код закончен, когда функции очень гладкие ~

Итак, давайте посмотрим, что еще есть в документе promiseA+.

  • resolvePromiseЭта функция будет решатьpromise2какое состояние использовать, еслиxявляется общим значением, затем напрямую используйтеx,еслиxЯвляетсяpromiseтогда поставь этоpromiseстатус какpromise2положение дел
  • судить, еслиxа такжеpromise2это объект, т.promise2 === x, то он попадает в вызов цикла, на этот разpromise2возьметTypeErrorдляreasonпревратиться вrejected
  • еслиxЯвляетсяpromise,Такpromise2просто принятьxсостояние, с иxидентичныйvalueидтиresolve, или с иxидентичныйreasonидтиreject
  • еслиxявляется объектом или функцией, затем выполнить сначалаlet then = x.then
  • еслиxне является объектом или функцией, тоresolveэтоx
  • Если при выполнении вышеуказанного оператора сообщается об ошибке, используйте эту причину ошибки, чтобыreject promise2
  • еслиthenявляется функцией, затем выполнитеthen.call(x,resolveCallback,rejectCallback)
  • еслиthenне является функцией, тоresolveэтоx
  • еслиxдаfulfilledгосударство, затем идиresolveCallbackЭта функция в настоящее время будет успешной по умолчанию.valueкак параметрyПерейти кresolveCallback,которыйy=>resolvePromise(promise2,y), продолжайте звонитьresolvePromiseЭта функция гарантирует, что возвращаемое значение является обычным значением, а неpromise
  • еслиxдаrejectedзатем указать причину этого отказаreasonтак какpromise2причина отказаrejectвыйди
  • еслиresolveCallback,rejectCallbackЭти две функции уже были вызваны или вызываются несколько раз с одними и теми же параметрами, затем обязательно вызывайте только первый раз и игнорируйте остальные.
  • если звонишьthenвыдает исключение, и еслиresolveCallback,rejectCallbackЭти две функции уже были вызваны, затем игнорируйте это исключение, в противном случае используйте это исключение какpromise2изrejectпричина

Мы так много резюмировали снова и снова, что ж, начнем, не говоря, сколько мы резюмировали.

/**
 * 用来处理then方法返回结果包装成promise 方便链式调用
 * @param {*} promise2 then方法执行产生的promise 方便链式调用
 * @param {*} x then方法执行完成功回调或者失败回调后的result
 * @param {*} resolve 返回的promise的resolve方法 用来更改promise最后的状态
 * @param {*} reject 返回的promise的reject方法 用来更改promise最后的状态
 */
function resolvePromise(promise2,x,resolve,reject){
    // 首先判断x和promise2是否是同一引用 如果是 那么就用一个类型错误作为Promise2的失败原因reject
    if( promise2 === x) return reject(new TypeError('typeError:大佬,你循环引用了!'));
    // called 用来记录promise2的状态改变,一旦发生改变了 就不允许 再改成其他状态
    let called;
    if( x !== null && ( typeof x === 'object' || typeof x === 'function')){
        // 如果x是一个对象或者函数 那么他就有可能是promise 需要注意 null typeof也是 object 所以需要排除掉
        //先获得x中的then 如果这一步发生异常了,那么就直接把异常原因reject掉
        try {
            let then = x.then;//防止别人瞎写报错
            if(typeof then === 'function'){
                //如果then是个函数 那么就调用then 并且把成功回调和失败回调传进去,如果x是一个promise 并且最终状态时成功,那么就会执行成功的回调,如果失败就会执行失败的回调如果失败了,就把失败的原因reject出去,做为promise2的失败原因,如果成功了那么成功的value时y,这个y有可能仍然是promise,所以需要递归调用resolvePromise这个方法 直达返回值不是一个promise
                then.call(x,y => {
                    if(called) return;
                    called = true;
                    resolvePromise(promise2,y,resolve,reject)
                }, error=>{
                    if(called) return
                    called = true;
                    reject(error)
                })
            }else{
                resolve(x)
            }
        } catch (error) {
            if(called) return
            called = true;
            reject(error)
        }
    }else{
        // 如果是一个普通值 那么就直接把x作为promise2的成功value resolve掉
        resolve(x)
    }

}

finnnnnnnnn наконец-то мы достигли спецификации PromiseA+ благодаря нашим неустанным усилиям.Promise!

В конце концов, для совершенства мы должныpromiseосуществленныйPromise.resolve,Promise.reject,так же какcatch,Promise.allа такжеPromise.raceэти методы.

Некоторые методы промисов

Promise.resolve = function(value){
    return new Promise((resolve,reject)=>{
        resolve(value)
    })
}
Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason)
    })
}
Promise.prototype.catch = function(onRejected){
    return this.then(null,onRejected)
}
Promise.all = function(promises){
    return new Promise((resolve,reject)=>{
        let arr = [];
        let i = 0;
        function getResult(index,value){
            arr[index] = value;
            if(++i == promises.length) {
                resolve(arr)
            }
        }
        for(let i = 0;i<promises.length;i++){
            promises[i].then(data=>{
                getResult(i,data)
            },reject)
        }
    })
}
Promise.race = function(promises){
    return new Promise((resolve,reject)=>{
        for(let i = 0 ; i < promises.length ; i++){
            promises[i].then(resolve,reject)
        }
    })
}

Обещание синтаксического сахара

Сразу после того, как арбуз перешел к жевательному сахару, синтаксический сахар должен заставить нас писать промисы быстрее, поэтому был внесен ряд изменений.Давайте рассмотрим пример, например, когда мы инкапсулируем функцию ширины и высоты, которая асинхронно считывает картинки

    // 原来的方式
    let getImgWidthHeight = function(imgUrl){
        return new Promise((resolve,reject)=>{
            let img = new Image();
            img.onload = function(){
                resolve(img.width+'-'+img.height)
            }
            img.onerror = function(e){
                reject(e)
            }
            img.src = imgUrl;
        })
    }

Вы чувствуете, что это немного удобно писать, но также и немного неудобно, как будто я должен каждый раз писать актуатор! Зачем! Ладно, не за что, раз неудобно, будем менять!

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

С приведенным выше синтаксическим сахаром давайте посмотрим, как написать функцию этой картинки.

    let newGetImgWidthHeight = function(imgUrl){
        let dfd = Promise.defer();
        let img = new Image();
        img.onload = function(){
            dfd.resolve(img.width+'-'+img.height)
        }
        img.onerror = function(e){
            dfd.reject(e)
        }
        img.url = imgUrl;
        return dfd.promise
    }

Вы обнаружили, что нам не хватает уровня вложенности функций, да~~ хорошо~~

Окончательная проверка

npm install promises-aplus-tests -g

Поскольку мы все говорили, что следуем спецификации promiseA+, мы должны как минимум привести какие-то доказательства.promiseКак это! После завершения установки запустим нашpromise

Наконец выбежали, и мы все прошли тест! прохладно! Добавьте на ужин куриные бедра~