Говоря об обещаниях для вопросов интервью и практических приложений

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

«Золото-три, серебро-четыре, золото-девять, серебро-десять» — это сезоны сбора урожая. Столкнувшись с различными вопросами на собеседовании, приходится запоминать различные концепции и принципы, что довольно скучно. В этой статье мы поговорим об обещаниях для вопросов на собеседовании и о практическом использовании.

Что такое обещания?

Обещание — важное понятие в асинхронном программировании JS, оно абстрагирует асинхронную обработку объектов и является одним из наиболее популярных решений для асинхронного программирования Javascript. Это предложение дает понять, что Promise — это идея, решение или метод объекта для решения асинхронных задач. В js, где часто используется асинхронность, используется Ajax-взаимодействие. Например, в эпоху es5,jQueryизajaxиспользованиеsuccessсделать это асинхронно:

$.ajax({
   url:'/xxx',
   success:()=>{},
   error: ()=>{}
})

Этот метод может ясно дать людям, читающим код, понять, какая часть является функцией обратного вызова успешного запроса Ajax и функцией обратного вызова неудачного. Но тут возникает проблема: когда запрос должен непрерывно запрашивать несколько интерфейсов, этот код кажется беспорядочным:

// 第一次 
$.ajax({
     url:'/xxx',
     success:()=>{
         // 第二次
         $.ajax({
             url:'/xxx',
             success:()=>{
               // 第三次
               $.ajax({
                  url:'/xxx',
                  success:()=>{
                   // 可能还会有
                  },
                  error: ()=>{}
                })
             },
             error: ()=>{}
        })
     },
     error: ()=>{}
}) 

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

Конечно, это эпоха es5. Когда язык js перешел в эпоху es6, появление промисов внесло изменения в асинхронность. Promise предоставляет функцию обратного вызова для async:

$.ajax({
    url:'/xxx',
}).then( ()=>{
   // 成功的回调
}, ()=>{
  // 失败的回调 
})

Дополнительный момент заключается в том, что вы можете продолжать писать объект Promise в методе then и return, а затем продолжать вызывать then для выполнения операции обратного вызова.

Использование обещаний

После разговора о том, что такое промисы, давайте изучим, как их использовать. Прежде всего, Promise — это объект, поэтому мы используем новый метод для создания нового объекта. Затем передайте ему функцию в качестве параметра.Эта функция также имеет два параметра, один называется resolve (решение), а другой называется reject (отказ).Эти два параметра тоже являются функциями. Затем мы используем then для вызова промиса:

const fn = new Promise(function (resolve, reject) {
  setTimeout(()=>{
    let num = Math.ceil(Math.random() * 10) // 假设num为7
    if (num > 5) {
      resolve(num) //返回7
    } else {
      reject(num)
    }
  },2000)
})
fn.then((res)=>{
  console.log(res) // 7
},(err)=>{
  console.log(err)
})

Это самое простое использование промисов. Предположим, что случайное число, сгенерированное через 2 секунды, равно 7, поэтому функция обратного вызова разрешения запускается, а затем переходит к первой функции, console.log(7). Предположим, что случайное число, сгенерированное через 2 секунды, равно 3, поэтому запускается функция обратного вызова reject, а затем переходит ко второй функции, console.log(3).

Тогда вы, возможно, сказали, что если Promise способен только на это, это не имеет большого значения, верно? Выше мы говорили, что продвинутая точка Promise заключается в том, что вы можете продолжать писать объект Promise в методе then и return, а затем продолжать вызывать then для выполнения операции обратного вызова:

fn = new Promise(function (resolve, reject) {
  let num = Math.ceil(Math.random() * 10)
  if (num > 5) {
    resolve(num)
  } else {
    reject(num)
  }
})
// 第一次回调
fn.then((res)=>{
  console.log(`res==>${res}`)
  return new Promise((resolve,reject)=>{
    if(2*res>15){
      resolve(2*res)
    }else{
      reject(2*res)
    }
  })
},(err)=>{
  console.log(`err==>${err}`)
}).then((res)=>{ // 第二次回调
  console.log(res)
},(err)=>{
  console.log(`err==>${err}`)
})

Это может заменить создание ада вложенных обратных вызовов, аналогичного успеху jQuery в эпоху es5 выше, что сделает код намного чище. Решение здесь эквивалентно предыдущему успеху.

Принцип обещаний

Внутри Promise есть менеджер состояний с тремя состояниями: ожидание, выполнено и отклонено.

(1) Состояние инициализации объекта обещания находится на рассмотрении.

(2) Когда вызывается разрешение (успех), оно будет отложено => выполнено.

(3) Когда вызывается отклонение (сбой), оно будет находиться в ожидании => отклонено.

Таким образом, просмотр resolve(num) в приведенном выше коде фактически меняет состояние обещания с pending на выполнено, а затем передает значение в функцию успешного возврата then и отклоняет наоборот. Но что нужно помнить, так это то, что состояние обещания может быть изменено только путем pending => выполнено/отклонено, после того, как оно изменено, его нельзя изменить (помните, помните, это будет протестировано ниже).

Когда статус выполнен (отклонен и наоборот), будет вызвана успешная функция обратного вызова, и она примет переданное выше число, а затем будет работать. Метод promise.then возвращает новый объект обещания каждый раз, когда он вызывается, поэтому его можно объединить в цепочку (будь то разрешение или отклонение).

Несколько способов обещания

then

Метод then используется для регистрации функции обратного вызова, когда статус становится выполненным или отклоненным:

// onFulfilled 是用来接收promise成功的值
// onRejected 是用来接收promise失败的原因
promise.then(onFulfilled, onRejected);

Следует отметить, что метод then выполняется асинхронно.

// resolve(成功) onFulfilled会被调用
const promise = new Promise((resolve, reject) => {
   resolve('fulfilled'); // 状态由 pending => fulfilled
});
promise.then(result => { // onFulfilled
    console.log(result); // 'fulfilled' 
}, reason => { // onRejected 不会被调用
})

// reject(失败) onRejected会被调用
const promise = new Promise((resolve, reject) => {
   reject('rejected'); // 状态由 pending => rejected
});
promise.then(result => { // onFulfilled 不会被调用
}, reason => { // onRejected 
    console.log(rejected); // 'rejected'
})

catch

Catch может перехватывать исключение, отправленное в предыдущем, а затем в цепочном методе записи.

fn = new Promise(function (resolve, reject) {
  let num = Math.ceil(Math.random() * 10)
  if (num > 5) {
    resolve(num)
  } else {
    reject(num)
  }
})
fn..then((res)=>{
  console.log(res)
}).catch((err)=>{
  console.log(`err==>${err}`)
})

На самом деле поймать эквивалентноthen(null,onRejected), первое является просто синтаксическим сахаром для второго.

решить, отклонить

Promise.resolve возвращает выполненное обещание, а Promise.reject возвращает отклоненное обещание.

Promise.resolve('hello').then(function(value){
    console.log(value);
});

Promise.resolve('hello');
// 相当于
const promise = new Promise(resolve => {
   resolve('hello');
});

// reject反之

all

Но в буквальном смысле это может означать, что такое состояние.Позвольте мне взглянуть на его использование, и вы сможете понять этот статический метод:

var   p1 = Promise.resolve(1),
      p2 = Promise.reject(2),
      p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then((res)=>{
    //then方法不会被执行
    console.log(results);
}).catch((err)=>{
    //catch方法将会被执行,输出结果为:2
    console.log(err);
});

Возможно, как только один из нескольких обещанных объектов в качестве параметров будет отклонен, возвращаемое значение всех будет отклонено.

При выполнении статуса возврата этих функций как параметров, что касается времени вывода, то это зависит от того, кто работает медленнее:

let p1 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('1s') //1s后输出
    resolve(1)
  },1000)
})
let p10 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('10s') //10s后输出
    resolve(10)
  },10000)
})
let p5 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('5s') //5s后输出
    resolve(5)
  },5000)
})
Promise.all([p1, p10, p5]).then((res)=>{
    console.log(res); // 最后输出
})

Когда этот код запускается, он выводит [1,10,5] через 10 секунд в соответствии с принципом того, кто работает медленнее. кончено, на этом все кончено.

race

Метод promise.race() также может обрабатывать массив экземпляров обещаний, но он отличается от promise.all(). Буквально это означает гонки, поэтому его гораздо проще понять, то есть экземпляр элемента в массив Тот, кто изменит состояние первым, передаст состояние и асинхронный результат вниз. Однако остальные продолжатся.

let p1 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('1s') //1s后输出
    resolve(1)
  },1000)
})
let p10 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('10s') //10s后输出
    resolve(10) //不传递
  },10000)
})
let p5 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('5s') //5s后输出
    resolve(5) //不传递
  },5000)
})
Promise.race([p1, p10, p5]).then((res)=>{
    console.log(res); // 最后输出
})

Итак, в конце этого кода наш результат

1s
1
5s
10s

Мы можем выполнять операции тайм-аута на основе атрибута гонки:

//请求某个图片资源
let requestImg = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
    });
//延时函数,用于给请求计时
let timeOut = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('图片请求超时');
        }, 5000);
    });

Promise.race([requestImg, timeout]).then((res)=>{
    console.log(res);
}).catch((err)=>{
    console.log(err);
});

Вопросы, связанные с обещаниями на собеседовании

1.

const promise = new Promise((resolve, reject) => {
    console.log(1);
    resolve();
    console.log(2);
});
promise.then(() => {
    console.log(3);
});
console.log(4);

Выходной результат: 1, 2, 4, 3.

Идея решения проблемы: метод then выполняется асинхронно.

2.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
    reject('error')
  }, 1000)
})
promise.then((res)=>{
  console.log(res)
},(err)=>{
  console.log(err)
})

Выходной результат: успех

Идея решения проблемы: как только состояние Promise изменилось, его нельзя изменить.

3.

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

Выходной результат: 1

Идея решения проблемы: Ожидаемый параметр метода then в Promise — это функция, и если передать нефункцию, произойдет проникновение значения.

4.

setTimeout(()=>{
  console.log('setTimeout')
})
let p1 = new Promise((resolve)=>{
  console.log('Promise1')
  resolve('Promise2')
})
p1.then((res)=>{
  console.log(res)
})
console.log(1)

Выходной результат:

Обещание1
1
Обещание2
setTimeout

Идея решения проблемы: это связано с проблемой очереди выполнения js. Весь код скрипта помещается в очередь макрозадач. Когда setTimeout выполняется, будет создана новая очередь макрозадач. Однако promise.then помещается в другую очередь задач.microtask queueсередина. Механизм выполнения скрипта возьмет задачу из очереди макрозадач и выполнит ее. затем поместите всеmicrotask queueПосле выполнения последовательности возьмите очередь макрозадач, в которой находится setTimeout, и начните выполнение последовательно. (конкретная ссылкаУуху. Call.com/question/36…)

5.
Promise.resolve(1)
    .then((res) => {
        console.log(res);
        return 2;
    })
    .catch((err) => {
        return 3;
    })
    .then((res) => {
        console.log(res);
    });

Выходной результат: 1 2

Идея решения проблемы: Обещайте сначала разрешить (1), а затем выполнить функцию then, чтобы она вывела 1, а затем вернула 2 в функции. Поскольку это функция разрешения, последующая функция catch не будет выполняться, но вторая функция then будет выполняться напрямую, поэтому будет выведено 2.

6.

const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('开始');
resolve('success');
}, 5000);
});
 
const start = Date.now();
promise.then((res) => {
console.log(res, Date.now() - start);
});
 
promise.then((res) => {
console.log(res, Date.now() - start);
});

Выходной результат:

Начинать

успех 5002

успех 5002

Идея решения проблемы: обещание.thenили.catchМожет вызываться несколько раз, но здесь конструктор Promise выполняется только один раз. Другими словами, как только внутреннее состояние обещания изменится и получит значение, каждый последующий вызов.then или.catchполучит значение напрямую.

7.

let p1 = new Promise((resolve,reject)=>{
  let num = 6
  if(num<5){
    console.log('resolve1')
    resolve(num)
  }else{
    console.log('reject1')
    reject(num)
  }
})
p1.then((res)=>{
  console.log('resolve2')
  console.log(res)
},(rej)=>{
  console.log('reject2')
  let p2 = new Promise((resolve,reject)=>{
    if(rej*2>10){
      console.log('resolve3')
      resolve(rej*2)
    }else{
      console.log('reject3')
      reject(rej*2)
    }
  })
  return p2
}).then((res)=>{
  console.log('resolve4')
  console.log(res)
},(rej)=>{
  console.log('reject4')
  console.log(rej)
})

Выходной результат:

отклонить1
отклонить2
решить3
решить4
12

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

8. Главное событие! ! ! ! Реализовать простое обещание
function Promise(fn){
  var status = 'pending'
  function successNotify(){
      status = 'fulfilled'//状态变为fulfilled
      toDoThen.apply(undefined, arguments)//执行回调
  }
  function failNotify(){
      status = 'rejected'//状态变为rejected
      toDoThen.apply(undefined, arguments)//执行回调
  }
  function toDoThen(){
      setTimeout(()=>{ // 保证回调是异步执行的
          if(status === 'fulfilled'){
              for(let i =0; i< successArray.length;i ++)    {
                  successArray[i].apply(undefined, arguments)//执行then里面的回掉函数
              }
          }else if(status === 'rejected'){
              for(let i =0; i< failArray.length;i ++)    {
                  failArray[i].apply(undefined, arguments)//执行then里面的回掉函数
              }
          }
      })
  }
  var successArray = []
  var failArray = []
  fn.call(undefined, successNotify, failNotify)
  return {
      then: function(successFn, failFn){
          successArray.push(successFn)
          failArray.push(failFn)
          return undefined // 此处应该返回一个Promise
      }
  }
}

Идея решения проблемы: resolve и reject в Promise используются для изменения состояния Promise и передачи параметров, тогда параметры должны быть функциями, которые выполняются как обратные вызовы. Поэтому, когда обещание меняет состояние, вызывается функция обратного вызова, и функция обратного вызова, которая должна быть выполнена, выбирается в соответствии с другим состоянием.

Суммировать

Во-первых, Обещание — это объект, точно так же, как и его буквальное значение, представляет собой время, когда результат будет известен в определенное время в будущем, и не подвержен влиянию внешних факторов. Как только промис срабатывает, его состояние может стать только выполненным или отклоненным, и это изменение было необратимым. Конструктор Promise принимает функцию в качестве параметра.Два параметра функции параметра - разрешение и отклонение соответственно.Его функция заключается в преобразовании состояния обещания из ожидающего в выполненное или отклоненное, а также в передаче возвращаемого значения успеха или провал. Затем есть две функции в качестве callback-функции при изменении состояния промиса: при изменении состояния промиса он принимает переданные параметры и вызывает соответствующую функцию. Процесс обратного вызова в этом случае является асинхронной операцией. Метод catch представляет собой инкапсуляцию (синтаксический сахар) .then(null, rejectFn), который используется для указания функции обратного вызова при возникновении ошибки. Вообще говоря, рекомендуется не определять callback-функцию отклоненного состояния в then, а вместо этого использовать метод catch. Все и гонка - обе функции гонки. Время окончания всех зависит от самой медленной. Как только функция промиса как параметр имеет состояние отклонено, состояние всего промиса отклонено, а время окончания гонки зависит от самый быстрый, как только состояние самого быстрого промиса изменится, состояние его общего промиса станет соответствующим состоянием, а остальные промисы с параметрами продолжат работу.

Конечно, в эпоху es7 появилось и асинхронное решение await/async, о котором речь пойдет позже.