Что интервьюер хочет знать, когда он спрашивает обещание

JavaScript
Что интервьюер хочет знать, когда он спрашивает обещание

передняя часть Seewo ENOW

Официальный сайт компании:CVTE (Гуанчжоу CVTE)

Команда: enow team центра программных платформ Seewo для будущего образования в рамках CVTE

Автор этой статьи:

瀚程名片.png

предисловие

Promise — это решение для асинхронного программирования, которое можно рассматривать как контейнер, в котором хранятся результаты будущих событий. Он имеет три состояния:pending(в ходе выполнения),fulfilled(успешно) иrejected(Не удалось), после того как состояние изменилось, его нельзя изменить снова.

Что такое ад обратного вызова?

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

http.post(data,function(res) {
    // do something here
})

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

http.post(data,function(res1) {
  http.post(res1,function(res2) {
    // do something here
  })
})

По мере усложнения продукта и бизнес-логики этот код может породить:

http.post(data,function(res1){
	http.post(res1,function(res2){
    http.post(res2,function(res3){
      http.post(res3,function(res4){
        http.post(res4,function(res5){
          http.post(res5,function(res6){
              // do something here
          })
        })
      })  
    })  
  })
})

это печально известноад обратного звонкаНегативные последствия очевидны:

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

Используя обещания, мы можем написать это:

fetch(data).then(res1 => {
  return fetch(res1);
}).then(res2 => {
  return fetch(res2);
}).then(res3 => {
  return fetch(res3);
}).catch(err => {
  console.error('err: ', err);
})

Есть ли недостатки у обещаний?

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

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

Каков результат этого кода? Почему?

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve();
  setTimeout(() => {
    console.log(2);
  })
  reject('error');
})
promise.then(() => {
    console.log(3);
}).then(() => {
    console.log(5)
}).catch(e => console.log(e))
console.log(4);

Клише, то есть рассмотрение макрозадач и микрозадач. Основные моменты:

  • PromiseКод в теле функции выполняется синхронно
  • выполнить первымзадача макроса, а затем выполнитьмикрозадачи, выполнятьмикрозадачиПосле этого проверьте еще раз, есть ли что-то, что нужно выполнитьзадача макроса, и так далее
  • PromiseПосле изменения состояния его нельзя изменить снова

Правильный порядок вывода1、4、3、5、2

Могу ли я написать промисы от руки?

Вот простая реализация, которая может удовлетворитьthenметод прикованPromise:

class Promise {
  constructor(params) {
    //初始化state为pending
    this.state = 'pending';
    //成功的值,返回一般都是undefined
    this.value = undefined;
    //失败的原因,返回一般都是undefined
    this.reason = undefined;
    //成功执行函数队列
    this.onResolvedCallbacks = [];
    //失败执行函数队列
    this.onRejectedCallbacks = [];

    //success
    let resolve = value => {
      if (this.state === 'pending') {
        //state change
        this.state = 'fulfilled';
        //储存成功的值
        this.value = value;
        //一旦成功,调用函数队列
        this.onResolvedCallbacks.forEach(fn => fn());
      }
    };

    //error
    let reject = reason => {
      if (this.state === 'pending') {
        //state change
        this.state = 'rejected';
        //储存成功的原因
        this.reason = reason;
        //一旦失败,调用函数队列
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };
    try {
      params(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => {
      throw err
    };
    let promise2 = new Promise((resolve, reject) => {
      //当状态是fulfilled时执行onFulfilled函数
      if (this.state === 'fulfilled') {
        //异步实现
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      //当状态是rejected时执行onRejected函数
      if (this.state === 'rejected') {
        //异步实现
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      //当状态是pending时,往onFulfilledCacks、onRejectedCacks里加入函数
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          //异步实现
          setTimeout(() => {
            try {
              let x = onFulfilled(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);
            }
          }, 0)
        });
      };
    });
    return promise2;
  }
  catch(fn) {
    return this.then(null, fn);
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  //循环引用报错
  if (x === promise2) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  //防止多次调用
  let called;
  //判断x
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(x, y => {
          if (called) return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if (called) return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}
//resolve方法
Promise.resolve = function (val) {
  return new Promise((resolve, reject) => {
    resolve(val)
  });
}
//reject方法
Promise.reject = function (val) {
  return new Promise((resolve, reject) => {
    reject(val);
  });
}

const test = new Promise((res, rej) => {
  setTimeout(() => {
    res('resolve after 2000ms');
  }, 2000)
})

test.then(res => {
  console.error('res: ', res);	// res: resolve after 2000ms
})

Здесь наша функция обратного вызова используетsetTimeoutреализации, поместите их в очередь макрозадач, есть ли способ поместить их в очередь микрозадач? Как сделать? Заинтересованная детская обувь может попытаться добитьсяСпецификация Обещание/A+изPromise.

Существуют ли какие-либо другие асинхронные решения, кроме промисов?

также можно использоватьES6серединаGeneratorиметь дело с,Generator Реализация несколько похожа на реализацию традиционных языков программирования.сопрограмма.сопрограммаЭтапы выполнения примерно такие:

  • Корутина АНачать выполнение, выполнить до того места, где его нужно приостановить
  • Корутина АПриостановлено, исполнение переданоКорутина Б
  • Корутина БПосле выполнения вернуть право исполненияКорутина А
  • Корутина Авозобновить выполнение, вернуть результат

JavascriptАсинхронная задача аналогична приведенной выше.Корутина А, Разделен на два (или больше разделов) выполнено.

Generatorа такжеPromiseАналогично, можно рассматривать как контейнер, разница в том, чтоGeneratorконтейнер используется для храненияасинхронная задачавместоусловие. Если требуется асинхронная операция, используйтеyieldПросто отдайте контроль и используйтеnextметод может вернуть управление, возобновить выполнение иnextПараметры метода можно использовать как и предыдущийyieldВозвращаемое значение выражения.

Все тот же пример, мы используемGeneratorЧтобы получить волну:

function* getData(data) {
  const res1 = yield http.post(data);
  const res2 = yield http.post(res1);
  const res3 = yield http.post(res2);
  const res4 = yield http.post(res3);
  return http.post(res4);    
}

const g = getData(123);
const res1 = g.next(); 						// {value: res1,done: false}
const res2 = g.next(res1.value);	// {value: res2,done: false}
const res3 = g.next(res2.value);	// {value: res3,done: false}
const res4 = g.next(res3.value);	// {value: res4,done: false}
const res5 = g.next(res4.value);	// {value: res5,done: true}
const res6 = g.next()							// {value: undefined,done: true}

при звонкеgetData, он не возвращает результат, а возвращает объект-указательg. объект указателяgВверхnextметод, вы можете сделать так, чтобы внутренний указатель указывал на следующийyieldоператор и возвращает объект, представляющий текущее состояние выполнения.valueнедвижимость актуальнаyieldзначение выражения,doneСвойство указывает, завершен ли текущий обход. Когда обход окончен, если вы продолжаете звонитьnextметод, он вернетundefined.

также,GeneratorОн также может быть прекращен досрочно, просто позвоните вreturnметод, который возвращаетdoneсобственностьtrue, затем позвоните еще разnextметод всегда возвращаетdoneдляtrue.

function* getData(data) {
  yield 1;
  yield 2;
  yield 3;
}

const g = getData();
g.next();						// {value: 1, done: false}
g.return('done');		// {value: 'done', done: true}
g.next();						// {value: undefined, done: true}

Для отлова ошибок,GeneratorОшибки могут быть перехвачены извне

function* getData(data) {
  try{
		const res = yield data;
  } catch(error) {
    console.error('inner catch error: ', error);
  }
}

const g = getData(123);
g.next();

try {
  g.throw('err1');						// inner catch error: throw err
  g.throw('err2')					// outer catch error: throw err
} catch (error) {
  console.error('outer catch error: ', error);
}

Вы понимаете асинхронность/ожидание?

asyncЧто такое функция? Проще говоря, этоGeneratorСинтаксический сахар для функций.GeneratorИспользование все еще немного неясно, и оно всегда кажется немного сложным в использовании, поэтомуES7запущен вasync/await. Грамматика такжеGeneratorпохоже, просто*заменитьasync,yieldзаменитьawait. ноGeneratorвозвращает итератор иasync/awaitвозвращаетPromiseобъект, что означает, что вы можете использоватьthen,catchЖдём метод.

GeneratorВыполнение функции зависит от исполнителя. а такжеasyncУ функции есть свой исполнитель, которыйGeneratorфункция и автоисполнитель, завернутые в функцию, поэтому не нужно использоватьnextметод для постепенного управления выполнением функции, что согласуется с вызовом обычных функций.

async function getData(data) {
  const res1 = await fetch(data);
  const res2 = await fetch(res1);
  const res3 = await fetch(res2);
  const res4 = await fetch(res3);
  const res5 = await fetch(res4);
  return res5;
}

const finalRes = getData(123);

Суммировать

Окончательное решение асинхронного программирования заключается в обработке асинхронных операций, таких как синхронное программирование.

Справочная статья

Объект обещания

Функция генератора

асинхронная функция