Обещание: обещание, которое вы можете понять

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

Подробный исходный код Promise

Учиться знаниям значит уметь хорошо думать, думать и еще раз думать. - Эйнштейн

1. Ад обратного звонка

Когда-то наш код был таким, чтобы получить результат обратного вызова, нам нужно былоcallback hell, такой код блокировки можно назвать довольно отвратительным

let fs = require('fs')
fs.readFile('./a.txt','utf8',function(err,data){
  fs.readFile(data,'utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){
      console.log(data)
    })
  })
})

Наконец, наш盖世英雄Появился, он был одет в золотую броню и ехал на разноцветном благоприятном облаке. Ладно, прервем, да он нашPromise, тогда давайте посмотримPromiseКак после этого будет выглядеть приведенный выше код?

let fs = require('fs')
function read(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,'utf8',function(error,data){
      error && reject(error)
      resolve(data)
    })
  })
}

read('./a.txt').then(data=>{
  return read(data) 
}).then(data=>{
  return read(data)  
}).then(data=>{
  console.log(data)
})

Как показано выше

Действительно удобно, дрова есть? Итальянцы могут сказать даSwagдеформироваться. Так близко к теме, как можно написать такоеSwagРешение асинхронного артефакта?

2. Начните с фокуса, все маленькие глазки просматриваются

2.1 Promise/A+

Прежде всего, нам нужно знать, что мы пишемPromise, как писать, кто подскажет, как писать и какие правила соблюдать. Конечно, об этом можно не беспокоиться, ведь промышленность производит продукцию по набору правил и показателей.Promiseиз. Давайте посмотрим, что это такое. Портал ☞Promise/A+

2.2 constructor

Начнем с объявления класса с именемPromise, который является конструктором. Если у вас все еще есть проблемы с es6, вы можете перейти в блог Руан Да, чтобы изучить его (портал ☞es6)

class Promise{
  constructor(executor){
    //控制状态,使用了一次之后,接下来的都不被使用
    this.status = 'pendding'
    this.value = undefined
    this.reason = undefined
    
    //定义resolve函数
    let resolve = (data)=>{
      //这里pendding,主要是为了防止executor中调用了两次resovle或reject方法,而我们只调用一次
      if(this.status==='pendding'){
        this.status = 'resolve'
        this.value = data
      } 
    }

    //定义reject函数
    let reject = (data)=>{
      if(this.status==='pendding'){
        this.status = 'reject'        
        this.reason = data
      } 
    }

    //executor方法可能会抛出异常,需要捕获
    try{
      //将resolve和reject函数给使用者      
      executor(resolve,reject)      
    }catch(e){
      //如果在函数中抛出异常则将它注入reject中
      reject(e)
    }
  }
}

Затем я проанализирую функцию приведенного выше кода, принцип

  • executor:Вот примерPromiseПараметр, передаваемый в конструкторе объекта, обычно являетсяfunction(resolve,reject){}
  • status:``PromiseСостояние является состоянием ожидания по умолчанию в начале.Всякий раз, когда вызываются методы разрешения и отклонения, его значение будет изменено, которое позже будет использоваться в методе then.
  • value:После успешного обратного вызова разрешения вызовите значение параметра в методе разрешения.
  • reason:После успешного обратного вызова reject вызовите значение параметра в методе reject.
  • resolve:Объявите метод разрешения в конструкторе и передайте его через входящий метод исполнителя для обратного вызова пользователю.
  • reject:Объявите метод reject в конструкторе и передайте его через метод входящего исполнителя для обратного вызова пользователю.

2.3 then

тогда методPromiseСамый важный метод вPromiseЕсли получен результат разрешения или отклонения, то мы можем знать, что тогда метод здесь требует двух параметров, обратного вызова успеха и обратного вызова отказа, код выше!

then(onFufilled,onRejected){  
  if(this.status === 'resolve'){
    onFufilled(this.value)
  }
  if(this.status === 'reject'){
    onRejected(this.reason)
  }
}

Главное здесь - передать в конструкторе результаты resolve и rejectonFufilledиonRejected, обратите внимание, что эти два параметра передаются пользователем и являются методом. Так вы думали, что это было так просто? хочу большеSwagЧтобы иметь дело с различными сценариями, мы должны улучшить его. Продолжай спускаться!

3. Асинхронные обещания

Раньше мы имели дело только с промисами в синхронном случае, короче говоря, все операции не имеют асинхронных компонентов. Так что, если это асинхронно?

3.1 обратный звонок! ! ! !

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

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

//存放成功回调的函数
this.onResolvedCallbacks = []
//存放失败回调的函数
this.onRejectedCallbacks = []

let resolve = (data)=>{
  if(this.status==='pendding'){
    this.status = 'resolve'
    this.value = data
    //监听回调函数
    this.onResolvedCallbacks.forEach(fn=>fn())
  } 
}
let reject = (data)=>{
  if(this.status==='pendding'){
    this.status = 'reject'        
    this.reason = data
    this.onRejectedCallbacks.forEach(fn=>fn())
  } 
}

Затем необходимо добавить еще одно суждение о состоянии.Когда обещание является асинхронной операцией, функция обратного вызова должна быть добавлена ​​в массив функций обратного вызова, который мы определили ранее.

if(this.status === 'pendding'){
  this.onResolvedCallbacks.push(()=>{
    // to do....
    let x = onFufilled(this.value)
    resolvePromise(promise2,x,resolve,reject)
  })
  this.onRejectedCallbacks.push(()=>{
    let x = onRejected(this.reason)
    resolvePromise(promise2,x,resolve,reject)
  })
}

в порядке! Готово, асинхронность разрешена

3.2 resolvePromise

Это тожеPromiseГлавный момент, позвольте мне представить, когда мы используем Promise, мы можем обнаружить, что когда значение возвращается в функции then, мы можем продолжить с then, но какое значение можно получить в следующем then, и, когда мы тогда не вводите параметры, например:promise.then().then(), то следующее then все еще может получить значение, возвращаемое предыдущим then, теперь вы можете быть сбиты с толку. Позволь мне разгадать печали в твоем сердце,follow me.

then(onFufilled,onRejected){ 
    //解决onFufilled,onRejected没有传值的问题
    onFufilled = typeof onFufilled === 'function'?onFufilled:y=>y
    //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后then的resolve中捕获
    onRejected = typeof onRejected === 'function'?onRejected:err=>{ throw err ;}
    //声明一个promise对象
    let promise2
    if(this.status === 'resolve'){
      //因为在.then之后又是一个promise对象,所以这里肯定要返回一个promise对象
      promise2 = new Promise((resolve,reject)=>{
        setTimeout(()=>{
          //因为穿透值的缘故,在默认的跑出一个error后,不能再用下一个的reject来接受,只能通过try,catch        
          try{
            //因为有的时候需要判断then中的方法是否返回一个promise对象,所以需要判断
            //如果返回值为promise对象,则需要取出结果当作promise2的resolve结果
            //如果不是,直接作为promise2的resolve结果
            let x = onFufilled(this.value)
            //抽离出一个公共方法来判断他们是否为promise对象
            resolvePromise(promise2,x,resolve,reject)
          }catch(e){
            reject(e)
          }
        },0)
      })
    }
    if(this.status === 'reject'){
      promise2 = new Promise((resolve,reject)=>{
        setTimeout(()=>{
          try{
            let x = onRejected(this.reason)
            resolvePromise(promise2,x,resolve,reject)
          }catch(e){
            reject(e)
          }
        },0)
      })
    }
    if(this.status === 'pendding'){
      promise2 = new Promise((resolve,reject)=>{
        this.onResolvedCallbacks.push(()=>{
          // to do....
          setTimeout(()=>{
            try{
              let x = onFufilled(this.value)
              resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          },0)
        })
        this.onRejectedCallbacks.push(()=>{
          setTimeout(()=>{
            try{
              let x = onRejected(this.reason)
              resolvePromise(promise2,x,resolve,reject)
            }catch(e){
              reject(e)
            }
          })
        })
      })
    }
    return promise2
  }

Есть еще много способов сразу, не бойтесь, я объясню их по порядку

  1. возвращениеPromise? : Первое, на что нам нужно обратить внимание, это то, что then имеет возвращаемое значение, и оно все еще может находиться в then после then, а это значит, что предыдущий then return должен бытьPromise.
  2. Почему он покрыт слоем?setTimeout? : поскольку Promise сам по себе является асинхронным методом, он принадлежит столбцу микрозадач, и его значение должно быть получено после завершения стека выполнения, поэтому все возвращаемые значения должны быть завернуты в слой асинхронного setTimeout.
  3. Почему в начале два суда? : Это то, что мы хотели решить раньше.Если параметр в функции then не является функцией, то нам нужно это сделать. Если onFufilled не является функцией, вам нужно определить функцию для возврата предыдущего значения разрешения.Если onRejected не является функцией, пользовательская функция выдает исключение. Здесь будет небольшая яма, если здесь не будет выброшено исключение, то значение будет получено в onFufilled следующего потом. И поскольку здесь генерируется исключение, все onFufilled или onRejected требуют try/catch, что такжеPromise/A+Спецификация. Конечно, я думаю, что успешный обратный вызов не должен вызывать исключение, вы можете подумать об этом хорошенько.
  4. resolvePromiseчто это? : Это официальноPromise/A+требование. Потому что ваш потом может вернуть любую работу, в том числе конечноPromiseобъект, и если онPromiseобъект, нам нужно разобрать его до тех пор, пока он не станетPromiseобъект, принять его значение.

Итак, давайте посмотрим на этоresolvePromiseНа что это похоже.

function resolvePromise(promise2,x,resolve,reject){
  //判断x和promise2之间的关系
  //因为promise2是上一个promise.then后的返回结果,所以如果相同,会导致下面的.then会是同一个promise2,一直都是,没有尽头
  if(x === promise2){//相当于promise.then之后return了自己,因为then会等待return后的promise,导致自己等待自己,一直处于等待
    return reject(new TypeError('循环引用'))
  }
  //如果x不是null,是对象或者方法
  if(x !== null && (typeof x === 'object' || typeof x === 'function')){
    //为了判断resolve过的就不用再reject了,(比如有reject和resolve的时候)
    let called
    try{//防止then出现异常,Object.defineProperty
      let then = x.then//取x的then方法可能会取到{then:{}},并没有执行
      if(typeof then === 'function'){
        //我们就认为他是promise,call他,因为then方法中的this来自自己的promise对象
        then.call(x,y=>{//第一个参数是将x这个promise方法作为this指向,后两个参数分别为成功失败回调
          if(called) return;
          called = true
          //因为可能promise中还有promise,所以需要递归
          resolvePromise(promise2,y,resolve,reject)
        },err=>{
          if(called) return;
          called = true
          //一次错误就直接返回
          reject(err)
        })
      }else{
        //如果是个普通对象就直接返回resolve作为结果
        resolve(x)
      }
    }catch(e){
      if(called) return;
      called = true
      reject(e)
    }
  }else{
    //这里返回的是非函数,非对象的值,就直接放在promise2的resolve中作为结果
    resolve(x)
  }
}

Его функция состоит в том, чтобы судить и оценивать возвращаемое значение onFufilled и помещать последнее полученное значение в самый внешний слой.Promiseв функции разрешения.

  1. параметрpromise2(объект Promise, возвращаемый функцией then),x(возвращаемое значение функции onFufilled),resolve、reject(решить и отклонить самое внешнее Обещание).
  2. Зачем судить в первую очередьpromise2иx? : первый вPromise/A+Написано, что если два равны, то должно быть выброшено исключение. Я объясню, почему. Если два равны, мы можем посмотреть на следующем примере. Первый раз, когда p2 равен p1.Promiseобъект, этоPromiseКогда объект создан, вызывается функция resolvePromise(promise2,x,resolve,reject), и поскольку x равен самому себе, этоPromise, вам нужен метод then для рекурсии до тех пор, пока он неPromiseобъект, но результат x(p2) все еще ожидает, но он хочет выполнить свой метод then, что вызовет ожидание.
let p1 = new Promise((resolve,reject)=>{
  resolve()
})

let p2 = p1.then(d=>{
    return p2
})
  1. К чему призывают? : вызываемая переменная в основном используется для определения того,resolvePromiseФункция была разрешена или отклонена, поэтому нет необходимости выполнять следующее разрешение или отклонение.
  2. Зачем брать тогдашний атрибут? : Поскольку нам нужно определить, является ли x промисом, если атрибут then имеет нормальное значение, он будет разрешен напрямую.Если это функция, то это объект промиса, а затем нам нужно выполнить метод then этого х. Причина использования вызова заключается в том, что проблема, на которую указывает this в методе then.
  3. Почему вы звоните рекурсивно?resolvePromiseфункция? : Я считаю, что это нашли внимательные люди, здесь я использовал метод рекурсивного вызова, в первую очередь этоPromise/A+Во-вторых, это требования бизнес-сценария.Когда мы сталкиваемся с тем, что распознаватель промиса в распознавателе промиса содержит другой промис, нам нужно рекурсивно брать значение до тех пор, пока x не станет объектом промиса.

4. Улучшить обещания

Мы в основном завершили тогдашний метод Promise, так что теперь нам нужно посмотреть на другие его методы.

4.1 catch

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

//catch方法
catch(onRejected){
  return this.then(null,onRejected)
}

Этот метод зависает от прототипа Promise. Когда мы вызываем catch для передачи обратного вызова, это эквивалентно вызову метода then.

4.2 resolve/reject

Все должны были видетьPromise.resolve()、Promise.reject()Эти два использования, их роль на самом деле состоит в том, чтобы вернуть объект Promise, давайте реализуем это.

//resolve方法
Promise.resolve = function(val){
  return new Promise((resolve,reject)=>{
    resolve(val)
  })
}
//reject方法
Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  })
}

Эти два метода можно вызывать непосредственно через класс, принцип заключается в том, чтобы вернуть объект Promise с разрешением или отклонением внутри.

4.3 all

Можно сказать, что метод all является очень распространенным методом в Promise. Его функция состоит в том, чтобы поместить в него массив объектов Promise. Когда все разрешится, будет выполнен метод then. Когда есть отклонение, будет выполнен catch , и их результаты тоже расположены по порядку в массиве, поэтому давайте это реализуем.

//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
  let arr = []
  let i = 0
  function processData(index,data){
    arr[index] = data
    i++
    if(i == promises.length){
      resolve(arr)
    }
  }
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data)
      },reject)
    }
  })
}

Принцип состоит в том, чтобы взять массив в параметре и пройти по нему, и он будет выполняться всякий раз, когда выполнение будет успешным.processDataметод,processDataМетод используется для записи значения каждого промиса и его соответствующего нижнего индекса.Когда количество выполнений равно длине массива, выполняется разрешение, и затем присваивается значение arr. Здесь будет яма.Если судить о том, должно ли оно разрешаться по длине массива arr, то оно пойдет не так.Почему? Из-за характеристик массива js, если значение в позиции 1 будет введено в arr первым, в позиции 0 также будет пустое значение, поэтому это неразумно.

4.4 race

Хотя метод гонки используется редко, он также полезен в методе Promise.Его функция заключается в преобразованииPromiseМассив помещается в гонку, в зависимости от того, что выполняется первым, гонка выполняется напрямую, и значение берется оттуда. Давайте сделаем это.

//race方法
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    }
  })
}

Принцип должен быть понятен всем.Он очень прост.Это обход массива для выполнения Promise.Если есть одинPromiseРешите, если выполнение успешно.

Синтаксический сахар обещания отложен

Три слова "синтаксический сахар" должны быть знакомы каждому. Как фронтенд-инженер, который очень Swag, он должен быть знаком с братьями async/await. Да, это синтаксический сахар генератора. И синтаксический сахар, о котором мы будем говорить, это Promise.

//promise语法糖 也用来测试
Promise.deferred = Promise.defer = function(){
  let dfd = {}
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

Что оно делает? Посмотрите на код ниже, и вы узнаете

let fs = require('fs')
let Promise = require('./promises')
//Promise上的语法糖,为了防止嵌套,方便调用
//坏处 错误处理不方便
function read(){
  let defer = Promise.defer()
  fs.readFile('./1.txt','utf8',(err,data)=>{
    if(err)defer.reject(err)
    defer.resolve(data)
  })
  return defer.Promise
}

Верно, мы можем легко вызвать его синтаксический сахар in deferPromiseобъект. Так есть ли другой способ? Ответ положительный. Нам нужно установить плагин promises-aplus-tests глобальноnpm i promises-aplus-tests -g, а затем введите promises-aplus-tests [имя файла js], чтобы проверить спецификацию вашего промиса.

5. Конец

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