Наверное, самое понятное рукописное обещание

Promise

Введение

Эта статья написана для тех, кто имеет некоторый опыт использования промисов, если вы не использовали промисы, то эта статья может вам не подойти.Понимание использования промисов

Общая картина общей архитектуры этой статьи выглядит следующим образом.Далее мы будем реализовывать шаг за шагом.Promise.

Класс обещания

Прежде всего, обещание должно быть классом, и оно также определяетresolveа такжеrejectметод.

function Promise(executor) {
 // 初始化state为等待态    
 this.state = 'pending';
 // 成功的值
 this.value = undefined;
 // 失败的原因
 this.reason = undefined;
 // 存放 fn1 的回调
 this.fn1Callbacks = [];
 // 存放 fn2 的回调
 this.fn2Callbacks = [];
  // 成功
 let resolve = () => { };
 // 失败
 let reject = () => { };
 // 立即执行
 executor(resolve, reject);
}

Приведенный выше код реализуетPromiseТело конструктора, но с двумя проблемами:

  1. executorМогут быть ошибки, верно, в конце концов, это метод, переданный пользователем, подобный следующему. Если исполнитель делает ошибку, нам нужно использовать try catch, чтобы поймать ошибку, а Promise должен быть отклонен значением его throw:

    new Promise(function(resolve, reject) {
      console.log(a)  // a 没有被定义
    })
    
  2. resolve,rejectЭто все еще пустая функция, нам нужно добавить в нее логику.

Далее продолжаем улучшать:

function Promise(executor){
    // 初始化state为等待态
    this.state = 'pending';
    // 成功的值
    this.value = undefined;
    // 失败的原因
    this.reason = undefined;
    let resolve = value => {
        // state改变,resolve调用就会失败
        if (this.state === 'pending') {
            // resolve调用后,state转化为成功态
            this.state = 'fulfilled';
            // 储存成功的值
            this.value = value;
        }
    };
    let reject = reason => {
        // state改变,reject调用就会失败
        if (this.state === 'pending') {
            // reject调用后,state转化为失败态
            this.state = 'rejected';
            // 储存失败的原因
            this.reason = reason;
        }
    };
    // 如果executor执行报错,直接执行reject
    try{
        executor(resolve, reject);
    } catch (err) {
        reject(err);
    }
}

Подытожу картинкой:

Приведенный выше код не особенно сложен, следующийthenМетод немного сложный.

Реализовать метод then

Promiseобъект имеетthenМетод, используемый для регистрации обратного вызова после определения состояния этого промиса. когдаPromiseСостояние изменения было изменено, независимо от того, успешно оно выполнено или нет, оно будет вызваноthenметод

thenМетод используется следующим образом:

// then 方法传入两个方法作为参数,一个是fn1方法,一个是 fn2 方法
p1.then(function fn1(data){
    // fn1 方法的参数,用于获取promise对象的值
}, function fn2(err){
    // fn1 方法的参数,用于获取失败的原因
})

Из вышеприведенного примера ясно, что мы заключаем:

  1. thenметод можно найти вp1вызвал экземпляр. следовательноthenРеализация метода находится вPromiseизprototypeначальство.

  2. thenметод вернетPromise, И вернуть новый промис (Подробности) объект.

  3. Можно вызывать несколько разthenметод, то есть связанные вызовы, и каждый раз новыйPromiseобъект,PromiseСтатус не определен и может бытьfullfilled, может быть такжеresolve, в зависимости от того, какой вызовthenчас,fn1Возвращаемое значение.

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

Вот идея тогдашнего метода:

Давайте реализуемthenметод:

// then方法接收两个参数,fn1,fn2,分别为Promise成功或失败后的回调
Promise.prototype.then = function(fn1, fn2) {
  var self = this
  var promise2

  // 首先对入参 fn1, fn2做判断
  fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
  fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {
        //todo
    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
       //todo
    })
  }

  if (self.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {
       // todo
    })
  }
}

Во-первых, для входных параметровfn1, fn2выносить суждения. В спецификации сказано,fn1а такжеfn2являются необязательными параметрами.

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

Второй,PromiseВсего возможных состояний три, делим на триifблок для обработки, каждый из которых возвращаетnew Promise。

Итак, следующая логика:

  • еслиpromiseстатусresolved, нужно выполнитьfn1;

  • еслиpromiseстатусrejected, нужно выполнитьfn2;

  • еслиpromiseстатусpending, мы не уверены, что вызовfn1ещеfn2, вы можете сначала сохранить методы только вfn1Callback, fn2Callbackв массиве. Подождите, пока не будет определено состояние промиса, прежде чем его обрабатывать.

В соответствии с приведенной выше логикой заполните следующий код:

Promise.prototype.then = function(fn1, fn2) {
    var self = this
    var promise2
    fn1 = typeof fn1 === 'function' ? fn1 : function(v) {}
    fn2 = typeof fn2 === 'function' ? fn2 : function(r) {}
    if (self.status === 'resolved') {
        return promise2 = new Promise(function(resolve, reject) {
            // 把 fn1、fn2 放在 try catch 里面,毕竟 fn1、fn2 是用户传入的,报错嘛,很常见
            try {
                var x = fn1(self.data)
                // fn1 执行后,会有返回值,通过 resolve 注入到 then 返回的 promise 中
                resolve(x)
            } catch (e) {
                reject(e)                
            }
        })
    }
    if (self.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
            try {
                var x = fn2(self.data)
                reject(x)
            } catch (e) {
                reject(e)
            }
        })
    }
    if (self.status === 'pending') {
        return promise2 = new Promise(function(resolve, reject) {
            this.fn1Callback.push(function(value){
                try {
                    var x = fn1(self.data);
                    resolve(x)
                } catch (e) {
                    reject(e)
                }
            })
            this.fn2Callback.push(function(value) {
                try {
                    var x = fn2(self.data);
                    reject(x)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}
  • fn1, fn2Все это передается пользователем и может сообщить об ошибке, поэтому его следует поместить в try catch.

  • fn1, fn2Возвращаемое значение мы записываем какx, нейминг в спецификации тожеx, быть последовательным.xЭто значение будет часто использоваться в дальнейшем.

thenСуть функции в том, чтобыfn1возвращаемое значение, заключенное вpromiseВернитесь назад. Проблема в том,fn1Возвращаемое значение записывается разработчиком и может быть странным. В приведенном выше коде предполагается, чтоxявляется обычным значением. На самом деле, на самом деле,xБывают разные ситуации, с ними приходится разбираться отдельно:

  • еслиxявляется общим значением, как код выше, используйте его напрямуюresolveметод,thenможет вернуть нормальное обещание

    return new Promise((resolve) => {
        var x = fn1(self.data);    
        resolve(x)
    })
    
  • еслиxэто обещание, нужно дождаться этогоpromiseизменение состояния, получитьfullfilledценность . Затем мы снова меняем код и добавляем суждение

    return new Promise((resolve) => {
         var x = fn1(self.data);
         if (x instanceof Promise) {
            x.then((data) => {resolve(data)}, (e) => {reject(e)})
         } else {
             resolve(x)
         }
    })
    
  • Согласно правилам, мы должны быть совместимы со всеми видами стилей письма, например, еслиxявляется объектом, и объект имеетthenметод, так называемыйthenableобъект, мы должны иметь дело с ним следующим образом:

    return new Promise((resolve) => {
        var x = fn1(self.data);
        if (x instanceof Promise) {
            x.then((data) => {resolve(data)}, (e) => {reject(e)})
        } else if (typeof x.then === 'function'){
            x.then(function(y){
                resolve(y)
            }, function(e){
                reject(e)
            })
        } else {
            resolve(x)
        }
    })  
    

Выше мы добавили некоторую логику для обработки различных случаев, когда x возвращает значение. Нам нужно перенести эту логику вresolvePromiseметод,resolvePromiseОтвечает за размещение всех видов причудливыхxупаковано как обычноpromise.

resolvePromise

resolvePromiseметод, чтобыxзавернутый в обычное обещание

function resolvePromise(promise2, x, resolve, reject) {
    // 为了防止循环引用
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise!'));
    }
    // 如果 x 是 promise
    if (x instanceof Promise) {
        x.then(function (data) {
            resolve(data)
        }, function (e) {
            reject(e)
        });
        return;
    }
    
    // 如果 x 是 object 类型或者是 function
    if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
        // 拿x.then可能会报错
        try {
            // 先拿到 x.then
            var then = x.then;
            var called
            if (typeof then === 'function') {
                // 这里的写法,是 then.call(this, fn1, fn2)
                then.call(x, (y) => {
                    // called 是干什么用的呢?
                    // 有一些 promise 实现的不是很规范,瞎搞的,比如说,fn1, fn2 本应执行一个,
                    // 但是有些then实现里面,fn1, fn2都会执行
                    // 为了 fn1 和 fn2 只能调用一个, 设置一个 called 标志位
                    if (called) {
                        return;
                    }
                    called = true;
                    return resolvePromise(promise2, y, resolve, reject);
                }, (r) => {
                    if (called) {
                        return;
                    }
                    called = true;
                    return reject(r);
                });
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called) {
                return;
            }
            return reject(e);
        }
    } else {
        resolve(x);
    }
}

В приведенном выше коде необходимо обратить внимание на:

  • var then = x.thenЭта строка кода может сообщать об ошибке, поэтому вам нужно обернуть ее с помощью try catch. Почему можно получить ошибку при выборке свойств объекта?PromiseРеализаций много(bluebird, Q и т. д.), Promises/A+ — это просто спецификация, и все реализуют Promises в соответствии с этой спецификацией, чтобы быть универсальными, поэтому необходимо учитывать все возможные ошибки, предполагая, что объект Promise, реализованный другим человеком, используетObject.defineProperty()Злонамеренно выдавая ошибку при взятии значения, мы можем предотвратить ошибки в коде.

  • Если есть то в объекте, а то есть тип функции, то его можно рассматривать как объект промиса, тогда используйтеxВызовите метод then следующим образом.

  • еслиx === promise2, это вызовет циклическую ссылку, и если вы дождетесь завершения, будет сообщено об ошибке "циклическая ссылка". Что, если x и promise2 — одно и то же?

    let p2 = p1.then(function(data){
     console.log(data)
     return p2;
    })
    

    В приведенном выше примереp1.then()Возвращаемое значениеp2,fn1Возвращаемое значение такжеp2. В чем проблема с этим?promiseЕсли не вызывается вручнуюresolveметод, нет возможности изменить состояние.p2Вы не можете изменить свое состояние, вы не можете изменить свое состояние самостоятельно, и вы никогда не будетеfullfilled,rejected

  • Нам нужны разные реализации Promise, чтобы иметь возможность взаимодействовать друг с другом, т.е.fn1 / fn2Возвращаемое значение, x, рассматривается как объект, который может быть Promise, о чем говорится в стандарте.thenable, и вызовите метод then для x самым безопасным способом. Если все реализуют его по стандарту, то разные промисы могут взаимодействовать друг с другом. Стандарт должен быть на безопасной стороне, даже если x возвращает объект со свойством then, но не соответствует стандарту Promise (например, этот x вызывает оба параметра в своем then, синхронно или асинхронно (PS, в принципе, тогда The два параметра необходимости вызывать асинхронно, о чем будет сказано ниже), либо они вызываются после ошибки, либо тогда вообще не являются функцией), а также могут обрабатываться максимально корректно.

Затем функции должны выполняться асинхронно.

Наконец, мы только что сказали, что в принципеpromise.then(onResolved, onRejected)Двухфазные функции нужно вызывать асинхронно, по этому поводу в стандарте тоже естьиллюстрировать:

In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

Так как же превратить синхронный код в асинхронное выполнение? Вы можете использовать функцию setTimeout для ее имитации:

setTimeout(()=>{
    //此入的代码会异步执行
},0);

Используя эту технику, вы можете использовать setTimeout, чтобы сделать все места, где код выполняется асинхронно, например:

setTimeout(() => {
    try {
        let x = fn1(value);
        resolvePromise(promise2, x, resolve, reject);
    } catch (e) {
        reject(e);
    }
},0);

обещанная структура тела

// 1. 定义 status 状态
// 2. fn1, fn2 的数组
// 3. 定义 resolve reject 方法
// 4. executor 执行
function Promise(executor) {
    let self = this;
    
    self.status = 'pending';
    self.fn1Callback = [];
    self.fn2Callback = [];
    
    // resolve 做到事情
    // 1. 修改this 实例的状态
    // 2. 修改this 这里的data
    // 3. 遍历执行 this fn1Callback 上挂载的方法
    function resolve(value) {
        if (value instanceof Promise) {
            return value.then(resolve, reject);
        }
        setTimeout(() => { // 异步执行所有的回调函数
            if (self.status === 'pending') {
                self.status = 'resolved';
                self.data = value;
                for (let i = 0; i < self.fn1Callback.length; i++) {
                    self.fn1Callback[i](value);
                }
            }
        });
    }
    function reject(reason) {
        setTimeout(() => { // 异步执行所有的回调函数
            if (self.status === 'pending') {
                self.status = 'rejected';
                self.data = reason;
                for (let i = 0; i < self.fn2Callback.length; i++) {
                    self.fn2Callback[i](reason);
                }
            }
        });
    }
    
    try {
        executor(resolve, reject);
    } catch (reason) {
        reject(reason);
    }
}

// 1. 参数校验
// 2. 根据 statue, 执行 fn1, fn2 或者把 执行fn1, fn2的行为保存在数组
// 3. 把 fn1,fn2 的返回值, 使用 resolvePromise 包裹成 promise
Promise.prototype.then = function (fn1, fn2) {
    let self = this;
    let promise2;
    fn1 = typeof fn1 === 'function' ? fn1 : function (v) {
        return v;
    };
    fn2 = typeof fn2 === 'function' ? fn2 : function (r) {
        throw r;
    };
    
    // 执行到 then, 并不确定 promise 状态已经是 resolved
    if (self.status === 'resolved') {
        // then() 执行后,返回一个promise, promise 的值
        return promise2 = new Promise(((resolve, reject) => {
            setTimeout(() => { // 异步执行onResolved
                try {
                    // 执行 fn1(),拿到结果 x
                    // fn1是用户传入的,那fn1返回值, 可能性可就多了
                    let x = fn1(self.data);
                    // 如果 x 是简单值,直接 resolve(x);
                    // resolve(x);
                    // 需要使用 resolvePromise 方法封装
                    resolvePromise(promise2, x, resolve, reject);
                } catch (reason) {
                    reject(reason);
                }
            });
        }));
    }
    
    if (self.status === 'rejected') {
        return promise2 = new Promise(((resolve, reject) => {
            setTimeout(() => { // 异步执行onRejected
                try {
                    let x = fn2(self.data);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (reason) {
                    reject(reason);
                }
            });
        }));
    }
    
    if (self.status === 'pending') {
        // 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
        return promise2 = new Promise(((resolve, reject) => {
            // 先定义一个方法,把方法 挂载到 onResolvedCallback 数组上
            // 方法里面 就是 调用传入的 fn1
            self.onResolvedCallback.push((value) => {
                try {
                    let x = fn1(value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (r) {
                    reject(r);
                }
            });
            
            self.onRejectedCallback.push((reason) => {
                try {
                    let x = fn2(reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (r) {
                    reject(r);
                }
            });
        }));
    }
};

// 1. 普通值
// 2. promise 值
// 3. thenable 的值,执行 then
function resolvePromise(promise2, x, resolve, reject) {
    // 为了防止循环引用
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise!'));
    }
    // 如果 x 是 promise
    if (x instanceof Promise) {
        x.then(function (data) {
            resolve(data)
        }, function (e) {
            reject(e)
        });
        return;
    }
    
    // 如果 x 是 object 类型或者是 function
    if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
        // 拿x.then可能会报错
        try {
            // 先拿到 x.then
            var then = x.then;
            var called
            if (typeof then === 'function') {
                // 这里的写法,是 then.call(this, fn1, fn2)
                then.call(x, (y) => {
                    // called 是干什么用的呢?
                    // 有一些 promise 实现的不是很规范,瞎搞的,比如说,fn1, fn2 本应执行一个,
                    // 但是有些then实现里面,fn1, fn2都会执行
                    // 为了 fn1 和 fn2 只能调用一个, 设置一个 called 标志位
                    if (called) {
                        return;
                    }
                    called = true;
                    return resolvePromise(promise2, y, resolve, reject);
                }, (r) => {
                    if (called) {
                        return;
                    }
                    called = true;
                    return reject(r);
                });
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called) {
                return;
            }
            return reject(e);
        }
    } else {
        resolve(x);
    }
}

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

Promise.allнужно ждать всехpromiseстатус сталfulfilledтолько послеresolve, но пока естьpromiseЕсли это не удается, он возвращает результат ошибки.

Promise.all = function (arr) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(arr)) {
            throw new Error(`argument must be a array`)
        }
        let dataArr = [];
        let num = 0;
        for (let i = 0; i < arr.length; i++) {
            let p = arr[i];
            p.then((data) => {
                dataArr.push(data);
                num ++;
                if (num === arr.length) {
                    return resolve(data)
                }
            }).catch((e) => {
                return reject(e)
            })
        }
    })
}

Рукописное обещание.повторить попытку

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

Promise.retry = function(getData, times, delay) {
    return new Promise((resolve, reject) => {
        function attemp() {
            getData().then((data) => {
                resolve(data)
            }).catch((err) => {
                if (times === 0) {
                    reject(err)
                } else {
                    times--
                    setTimeout(attemp, delay)
                }
            })
        }
        attemp()
    })
}

полный тест

После окончательного написания исходного кода Promise действительно ли он соответствует окончательным требованиям?Обещания/спецификация A+, сообщество открытого исходного кода предоставляет пакет для тестирования нашего кода:promises-aplus-tests

Этот пакет может проверять, соответствует ли код, который мы написали, один за другим.Если какой-либо элемент несовместим, об этом будет сообщено нам.Если вы проверите свой код полностью зеленым, то поздравляем, ваш Proimse уже легальный, вы можете Доступно онлайн для использования другими.

Эта статья была опубликована по ссылке блога github:GitHub.com/Ду Цзюньчэн/…, будет обновляться в течение длительного времени в будущем, добро пожаловать, чтобы обратить внимание на звезду

Кроме того

ByteDance (Ханчжоу|Пекин|Шанхай) набирает много людей, льготы супер, уровень зарплаты НИМ, без часов на работе, ежедневный полдник, неограниченное количество бесплатных закусок, бесплатное трехразовое питание (читаю меню, волосатые краб, морское ушко, морской гребешок, морепродукты на гриле, кусочки рыбы, говяжья вырезка с черным перцем, говядина карри, острые раки), бесплатный тренажерный зал, сенсорная панель начального уровня с 15-дюймовым верхом с новым MBP и ежемесячное пособие на аренду жилья. В этот раз возможностей действительно много.Через год штат НИОКР увеличится в n раз.Техническая атмосфера хорошая,больших коров много,сверхурочных меньше.О чем вы сомневаетесь? Отправьте свое резюме по электронной почте ниже, сейчас!

Это всего лишь небольшой кусочек jd, больше добро пожаловать в WeChat~

Фронтенд JD:job.headline.com/is/B JM4работа…

бэкэнд JD:job.headline.com/is/bj jj ts работа…

тест JD:job.toutiao.com/Yes/B Ноутбук JF V9…

Продукт JD:job.toutiao.com/yes/не JB GV8job…

Стажер фронтенда:job.headline.com/is/BJ6NJ Работа…

Бэкенд-стажер:job.headline.com/is/BJ Diary RK работа…

Продолжать набирать большое количество интерфейсных, серверных, клиентских, тестовых, продуктовых, стажировочных агентств.

Резюме присылайте на dujuncheng@bytedance.com, предлагайте добавить WeChat dujuncheng1, можно поболтать о жизни, укажите, пожалуйста, откуда вы из Наггетса и где хотите разместить