предисловие
Несмотря на то, что в этом году исполнилось 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Имеет следующие особенности
-
promiseПо умолчанию есть три состоянияpendingсостояниеpendingможет статьfulfilled(состояние успеха) илиrejected(состояние отказа), и после преобразования его нельзя изменить на какое-либо другое значение. -
promiseесть один внутриvalueИспользуется для хранения результата успешного состояния -
promiseесть один внутриreasonИспользуется для хранения причины состояния отказа -
promiseпринять одинexecutorфункция, эта функция имеет два параметра, одинresolveметод, одинrejectметод при выполненииresolveчас,promiseстатус изменился наfulfilled,воплощать в жизньrejectчас,promiseстатус изменился наrejected - По умолчанию
new Promiseвнутренний во время выполненияexecutorвыполнение функции -
promiseВнутренняя поддержка асинхронного изменения состояния -
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- Если при выполнении вышеуказанного оператора сообщается об ошибке, используйте эту причину ошибки, чтобы
rejectpromise2- если
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
Наконец выбежали, и мы все прошли тест! прохладно! Добавьте на ужин куриные бедра~