предисловие
Несмотря на то, что в этом году исполнилось 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
- Если при выполнении вышеуказанного оператора сообщается об ошибке, используйте эту причину ошибки, чтобы
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
Наконец выбежали, и мы все прошли тест! прохладно! Добавьте на ужин куриные бедра~