Подтвердить что ты не робот

Promise callbacks возвращает promises

В отношении этих великих двух источников: NZakas - Возвращение Promises в Promise Chains и MDN Promises, я хотел бы спросить следующее:

Каждый раз, когда мы возвращаем значение из обработчика выполнения обещаний, как это значение передается новому обещанию, возвращенному от этого же обработчика?

Например,

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});

let p2 = new Promise(function(resolve, reject) {
    resolve(43);
});

let p3 = p1.then(function(value) {
    // first fulfillment handler
    console.log(value);     // 42
    return p2;
});

p3.then(function(value) {
    // second fulfillment handler
    console.log(value);     // 43
});

В этом примере p2 является обещанием. p3 также является обещанием, исходящим из обработчика выполнения p1. Однако p2 !== p3. Вместо этого p2 как-то волшебным образом решается на 43 (как?), И это значение затем передается в обработчик выполнения p3. Даже предложение здесь путается.

Не могли бы вы объяснить мне, что именно здесь происходит? Я полностью смущен этой концепцией.

4b9b3361

Ответ 1

Давайте скажем, что вызов внутри then() обратного вызова отклоняет обещание результата с отказом, а возврат из обратного вызова then() выполняет обещание результата с успешным значением.

let p2 = p1.then(() => {
  throw new Error('lol')
})
// p2 was rejected with Error('lol')

let p3 = p1.then(() => {
  return 42
})
// p3 was fulfilled with 42

Но иногда даже внутри продолжения мы не знаем, преуспели мы или нет. Нам нужно больше времени.

return checkCache().then(cachedValue => {
  if (cachedValue) {
    return cachedValue
  }

  // I want to do some async work here
})

Однако, если я выполняю асинхронную работу там, было бы слишком поздно для return или throw, не так ли?

return checkCache().then(cachedValue => {
  if (cachedValue) {
    return cachedValue
  }

  fetchData().then(fetchedValue => {
    // Doesn’t make sense: it’s too late to return from outer function by now.
    // What do we do?

    // return fetchedValue
  })
})

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

Это не означает, что в вашем примере p2 станет p3. Это отдельные объекты Promise. Однако, возвращая p2 из then(), который выражает p3, вы говорите "Я хочу, чтобы p3 разрешал все, что p2 разрешает, удастся или не удастся".

Как это происходит, его конкретная реализация. Внутренне вы можете думать о then() как о создании нового обещания. Реализация будет способна выполнять или отклонять ее, когда захочет. Обычно он автоматически выполняет или отклоняет его при возврате:

// Warning: this is just an illustration
// and not a real implementation code.
// For example, it completely ignores
// the second then() argument for clarity,
// and completely ignores the Promises/A+
// requirement that continuations are
// run asynchronously.

then(callback) {
  // Save these so we can manipulate
  // the returned Promise when we are ready
  let resolve, reject

  // Imagine this._onFulfilled is an internal
  // queue of code to run after current Promise resolves.
  this._onFulfilled.push(() => {
    let result, error, succeeded
    try {
      // Call your callback!
      result = callback(this._result)
      succeeded = true
    } catch (err) {
      error = err
      succeeded = false
    }

    if (succeeded) {
      // If your callback returned a value,
      // fulfill the returned Promise to it
      resolve(result)
    } else {
      // If your callback threw an error,
      // reject the returned Promise with it
      reject(error)
    }
  })

  // then() returns a Promise
  return new Promise((_resolve, _reject) => {
    resolve = _resolve
    reject = _reject
  })
}

Опять же, это очень псевдо-код, но показывает, как then() может быть реализован в реализациях Promise.

Если мы хотим добавить поддержку для решения Promise, нам просто нужно изменить код, чтобы иметь специальную ветвь, если callback вы переходите на then(), возвращает Promise:

    if (succeeded) {
      // If your callback returned a value,
      // resolve the returned Promise to it...
      if (typeof result.then === 'function') {
        // ...unless it is a Promise itself,
        // in which case we just pass our internal
        // resolve and reject to then() of that Promise
        result.then(resolve, reject)
      } else {
        resolve(result)
      }
    } else {
      // If your callback threw an error,
      // reject the returned Promise with it
      reject(error)
    }
  })

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

Ответ 2

В основном p3 есть return - другое обещание: p2. Это означает, что результат p2 будет передан как параметр для следующего обратного вызова then, в этом случае он решает 43.

Всякий раз, когда вы используете ключевое слово return, вы передаете результат в качестве параметра для следующего обратного вызова then.

let p3 = p1.then(function(value) {
    // first fulfillment handler
    console.log(value);     // 42
    return p2;
});

Ваш код:

p3.then(function(value) {
    // second fulfillment handler
    console.log(value);     // 43
});

Является равным:

p1.then(function(resultOfP1) {
    // resultOfP1 === 42
    return p2; // // Returning a promise ( that might resolve to 43 or fail )
})
.then(function(resultOfP2) {
    console.log(resultOfP2) // '43'
});

Btw, я заметил, что вы используете синтаксис ES6, у вас может быть более легкий синтаксис с использованием синтаксиса толстых стрелок:

p1.then(resultOfP1 => p2) // the `return` is implied since it a one-liner
.then(resultOfP2 => console.log(resultOfP2)); 

Ответ 3

В этом примере p2 является обещанием. p3 также является обещанием, исходящим из обработчика выполнения p1. Однако p2! == p3. Вместо этого p2 как-то волшебным образом разрешается до 43 (как?), И это значение затем передается обработчику выполнения p3. Даже предложение здесь путается.

упрощенная версия, как это работает (только псевдокод)

function resolve(value){
    if(isPromise(value)){
        value.then(resolve, reject);
    }else{
        //dispatch the value to the listener
    }
}

все это довольно сложно, так как вам нужно позаботиться о том, чтобы обещание уже было разрешено и еще несколько вещей.

Ответ 4

Я постараюсь ответить на вопрос "почему then обратные вызовы могут возвращать Promise сами" более канонические. Чтобы поднять другой угол, я сравниваю Promise с менее сложным и запутанным типом контейнера - Array s.

A Promise - это контейнер для будущего значения. Array - контейнер для произвольного количества значений.

Мы не можем применять обычные функции к типам контейнеров:

const sqr = x => x * x;
const xs = [1,2,3];
const p = Promise.resolve(3);

sqr(xs); // fails
sqr(p); // fails

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

xs.map(sqr); // [1,4,9]
p.then(sqr); // Promise {[[PromiseValue]]: 9}

Но что происходит, когда сама предоставленная функция возвращает контейнер одного и того же типа?

const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
const p = Promise.resolve(3);

xs.map(sqra); // [[1],[4],[9]]
p.then(sqrp); // Promise {[[PromiseValue]]: 9}

sqra действует как ожидаемый. Он просто возвращает вложенный контейнер с правильными значениями. Очевидно, это не очень полезно.

Но как интерпретировать результат sqrp? Если мы будем следовать своей собственной логике, это должно быть что-то вроде Promise {[[PromiseValue]]: Promise {[[PromiseValue]]: 9}} - но это не так. Итак, какая здесь магия?

Чтобы восстановить механизм, нам просто нужно немного адаптировать наш метод map:

const flatten = f => x => f(x)[0];
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];

xs.map(flatten(sqra))

flatten просто принимает функцию и значение, применяет функцию к значению и разворачивает результат, тем самым уменьшая структуру вложенного массива на один уровень.

Проще говоря, then в контексте Promise эквивалентен map в сочетании с flatten в контексте Array s. Такое поведение чрезвычайно важно. Мы можем применять не только нормальные функции к Promise, но также и функции, которые сами возвращают a Promise.

Фактически это область функционального программирования. A Promise - это конкретная реализация monad, then is bind/chain, а функция, возвращающая символ Promise, является монадической. Когда вы понимаете API Promise, вы в основном понимаете все монады.