Обещание в деталях

внешний интерфейс JavaScript Promise опрос
Обещание в деталях

      Promise— мой любимый синтаксис es6, о котором проще всего спросить на собеседовании. Так как же быть удобным в использовании и выделяться на собеседованиях?
Сначала зададим вопрос на собеседовании:

Вопрос из интервью: используйте Promise для инкапсуляции нативного ajax

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


function ajaxMise(url, method, data, async, timeout) {
    var xhr = new XMLHttpRequest()
    return new Promise(function (resolve, reject) {
        xhr.open(method, url, async);
        xhr.timeout = options.timeout;
        xhr.onloadend = function () {
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)
                resolve(xhr);
            else
                reject({
                    errorType: 'status_error',
                    xhr: xhr
                })
        }
        xhr.send(data);
        //错误处理
        xhr.onabort = function () {
            reject(new Error({
                errorType: 'abort_error',
                xhr: xhr
            }));
        }
        xhr.ontimeout = function () {
            reject({
                errorType: 'timeout_error',
                xhr: xhr
            });
        }
        xhr.onerror = function () {
            reject({
                errorType: 'onerror',
                xhr: xhr
            })
        }
    })
}

Введение в промисы

      Promiseэто объект, который содержит события, которые закончатся в будущем. У нее есть две характеристики, цитируемые из описания учителя Жуань Ифэн:

(1) Состояние объекта не зависит от внешнего мира. Объект Promise представляет собой асинхронную операцию с тремя состояниями: ожидание (выполняется), выполнено (успешно) и отклонено (сбой). Только результат асинхронной операции может определить текущее состояние, и никакая другая операция не может изменить это состояние. Это также происхождение названия Promise, что в переводе с английского означает «обещание», что означает, что другие средства не могут быть изменены.
(2) Как только состояние изменится, оно больше не изменится, и этот результат можно получить в любой момент. Есть только две возможности изменения состояния объекта Promise: с ожидания на выполнение и с ожидания на отклонение. Пока эти две ситуации имеют место, состояние затвердевает, оно больше не изменится, и всегда будет сохраняться результат, который называется разрешенным. Если изменение уже произошло, и вы добавляете функцию обратного вызова к объекту Promise, вы получите этот результат немедленно. Это совсем не то, что событие, характеристика события в том, что если вы пропустите его и послушаете снова, вы не получите результата.

Основное использование Promise
let promise1 = new Promise(function (resolve, reject){
    setTimeout(function (){
        resolve('ok') //将这个promise置为成功态(fulfilled),会触发成功的回调
    },1000)
})
promise1.then(fucntion success(val) {
    console.log(val) //一秒之后会打印'ok'
})
Самый простой код для реализации Promise
class PromiseM {
    constructor (process) {
        this.status = 'pending'
        this.msg = ''
        process(this.resolve.bind(this), this.reject.bind(this))
        return this
    }
    resolve (val) {
        this.status = 'fulfilled'
        this.msg = val
    }
    reject (err) {
        this.status = 'rejected'
        this.msg = err
    }
    then (fufilled, reject) {
        if(this.status === 'fulfilled') {
            fufilled(this.msg)
        }
        if(this.status === 'rejected') {
            reject(this.msg)
        }
    }

}
//测试代码
var mm=new PromiseM(function(resolve,reject){
    resolve('123');
});
mm.then(function(success){
    console.log(success);
},function(){
    console.log('fail!');
});

Micro-task / event loop

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

Вопрос для интервью: Напишите порядок вывода следующего кода

console.log('script start');

setTimeout(function () {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(function () {
    console.log('promise1');
}).then(function () {
    console.log('promise2');
});

console.log('script end');

Правильный ответ: «начало сценария», «конец сценария», «обещание1», «обещание2», «setTimeout». Причина в следующем:

  • setTimeout (или событие) регистрируется как задача, управляемая циклом событий.
  • Обещание регистрируется как микрозадача

      Event LoopЭто важный механизм js, то есть он встречает события илиsetTimeoutПосле ожидания соответствующая callback-функция будет помещена в очередь событий (очередь задач), а при выполнении основной программы функции из очереди будут поочередно помещаться в стек для выполнения. Вы можете обратиться к Учителю Жуань Ифэну.Подробное объяснение механизма работы JavaScript: снова поговорим о цикле событий, но похоже, что сайт учителя подвергся атаке и не восстановился.
ноPromiseВместо вышеописанного механизма она создает микрозадачу,micro-taskвыполнение всегда заканчивается в текущем стеке выполнения и следующемtaskПеред выполнением последовательность такова: «текущий стек выполнения» -> «микрозадача» -> «обратный вызов из очереди задач» -> «микрозадача» -> ... (непрерывное потребление очереди задач) -> « микрозадача», короче говоря, когда текущий стек выполнения пуст,micro-taskпропускной пункт.
Нижеmicro-taskОпределение:

Microtasks are usually scheduled for things that should happen straight after the currently executing script, such as reacting to a batch of actions, or to make something async without taking the penalty of a whole new task.

      Promiseзарегистрированmicro-task, поэтому в приведенном выше заголовке: сначала печатаются «начало сценария» и «конец сценария» в основном потоке, затем очищается очередь микрозадач, печатаются «обещание1», «обещание2», а затем вынимаетсяtask queueОбратный вызов в setTimeout выполняется, setTimeout печатает.

Почему появляются обещания

      PromiseОн предоставляет новое решение для асинхронного программирования js, потому что функция обратного вызова, которую мы использовали, на самом деле является большой проблемой, но она ограничена одним потоком js и должна быть написана в больших количествах. КонечноPromiseНе избавившись полностью от обратных вызовов, она просто изменила, где передавались обратные вызовы. Так что же не так с традиционными обратными вызовами?

вложенный

Упомянутая здесь вложенность означает, что большое количество функций обратного вызова затруднит чтение и изменение кода.Представьте себе сценарий: позвольте вам упомянуть следующий вызов url4 перед url2. Вам нужно очень аккуратно вырезать код и неуклюже вставлять, и вы не смеете изменять параметр result4, потому что это требует много дополнительной работы и сопряжено с риском.

$.ajax('url1',function success(result1){
    $.ajax('url2',function success(result2){
        $.ajax('url3',function success(result3){
            $.ajax('url4',function success(result4){
                //……
            })
        })
    })
})

Конечно, вышеописанная проблема несколько драматична, и в реальности такая сложная ситуация возникает редко. По сравнению с этим умственное непонимание, вызванное функциями обратного вызова, более смертоносно, потому что наш мозг предпочитает синхронную логику, поэтомуawaitПричина, по которой ключевые слова так популярны.
Помню, когда я делился новыми возможностями JS со своими одноклассниками, я сказал:awaitКлючевые слова, кто-то воскликнул: «Вау! Это неплохо, на этом можно писать код, как писать на java».

доверять

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

$.ajax('xxxxxx',function success(result1){
    //比如成功之后我会操作数据库记录结算金额
})

вышеjQueryсерединаajaxcall, мы ожидаем, что третья сторона (jQ) выполнит мою программу (обратный вызов) для нас после завершения некоторого события.
Ну, между нами и третьей стороной нет контракта или спецификации, которой нужно следовать, если только вы не прочитаете стороннюю библиотеку, которую хотите использовать, и не убедитесь, что она делает то, что вы хотите, но на самом деле трудно быть уверенным. Даже в нашем собственном коде или в инструментах, которые мы пишем, трудно быть заслуживающим 100% доверия.

Обещайте решение

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

  • решить: будетpromiseПереключиться из никогда не завершенного в успешное состояние, то есть из упомянутого вышеpendingпереключиться наfufilled,resolveПараметры можно передавать, следующий уровеньpromiseФункция успеха в получит его
  • отвергнуть: будетpromiseперейти из никогда не завершенного в неудачное состояние, т. е. изpendingпереключиться наrejected
let promise1 = new Promise(function(reslove, reject){
    //reslove或者reject或者出错
})
promise1.then(fufilled, rejected).then().then() //这是伪代码
promise1.then(fufilled, rejected)//可以then多次

function fufilled(data) {
    console.log(data)
}
function rejected(e){
    console.log(e)
}

Как и две функции, упомянутые выше, как только состояние изменяется, этоPromiseзавершил разрешение (больше не будет изменено) и возвращает новыйPromise, который можно связать. и может зарегистрировать несколькоthenметод, они решают одновременно и не влияют друг на друга. Этот дизайн значительно более элегантен, чем функции обратного вызова, и его легче понять и поддерживать. Так как же она улучшилась с точки зрения доверия?
      PromiseОтношение «инверсия управления» «инвертируется» обратно через механизм уведомления. Обратный вызов — это функция, которую я передаю третьей стороне, ожидая, что она будет выполнена для меня, когда произойдет событие, иPromiseРазве что, согласно предпосылке всех, следующих спецификации, я буду уведомлен, когда произойдет событие, и тогда я решу что-то сделать (выполнить какую-то функцию). Видите ли, есть принципиальная разница.
Кроме того, функция обратного вызова имеет следующие проблемы с доверием:PromiseОни также имеют связанные ограничения:

  • Обратный вызов вызван слишком рано
  • Обратный вызов вызван слишком поздно (или не вызван)
  • слишком много звонков
  • Никакие аргументы не были успешно переданы вашему обратному вызову
  • выплевывать ошибки или исключения
слишком рано или слишком поздно

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

new Promise(function (resolve) {
    resolve(111111);
})
Слишком много раз или параметры не переданы

      PromiseМожно принять решение только один раз, если вы примете решение несколько раз, она выполнит только первое решение, например:

new Promise(function (reslove, reject) {
    resolve()
    setTimeout(function () {
        resolve(2)
    },1000)
    resolve(3)
}).then(function (val) {
    console.log(val)   //undefined
})

Передаются параметры успешного обратного вызоваresolveПередается, например, как код выше, никакие параметры не передаются, тогда то, что получает val, будетundefined, значит, параметры все равно получены. Уведомление:resolveПринимается только один параметр, последующие параметры игнорируются.

глотать ошибки

      PromiseМеханизм обработки ошибок таков: если отображаемый вызовrejectИ передайте причину ошибки, это сообщение будет передано обратному вызову отклонения.
Кроме того, если в каком-либо процессе возникает ошибка (например, TypeError или ReferenceError), ошибка будет обнаружена, иPromiseОтклонение, то есть сообщение об ошибке также будет передано обратному вызову отклонения, который отличается от традиционного обратного вызова.При возникновении ошибки традиционный обратный вызов вызовет синхронный ответ, но если ошибки нет, он будет быть асинхронным.

обещание контроля параллелизма

все / раса

      allа такжеraceОбе функции выполняются одновременноpromiseметоды, их возвращаемое значение такжеpromise,allбуду ждать всехpromiseПосле того, как все решения приняты, иraceЭто то, что пока есть решение, оно будет разрешено.

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});

Примечание. Если параметр пуст,allМетод разрешения немедленно, иraceМетод будет зависать.

Вопрос из интервью: инкапсулировать метод promise.all
Promise.all = function(ary) {
    let num = 0
    let result = []
    return new Promise(function(reslove, reject){
        ary.forEach(promise => {
            promise.then(function(val){
                if(num >= ary.length){
                    reslove(result)
                }else{
                    result.push(val)
                    num++
                }
            },function(e){
                reject(e)
            })
        })
    })
}

thenalbe

Как определить, что объект является обещанием?

Ты можешь подуматьinstanceof Promise, но к сожалению нет. Причина в том, что каждая среда инкапсулирует свою собственнуюPromise, вместо использования родногоES6 Promise.
Итак, текущее решениеPromiseОдин из способов – определить, является лиthenableобъект (если это объект или функция и он имеетthenметод).
Это распространенный метод определения типа в js — определение типа утки:

Определение типа утки: если она выглядит как утка и крякает как утка, то это утка

resolve/reject

      resolveвозвращает немедленный успехPromise,rejectвозвращает немедленную ошибкуPromise,они естьnew Promiseсинтаксический сахар, поэтому следующие два выражения эквивалентны:

let p1 = new Promise(function(resolve, reject){
    reslove(11111)
})

let p2 = Promise.resolve(11111) //这和上面的写法结果一样

Кроме того, если входящиеresloveпараметр метода неpromiseноthenableзначение, тоresloveрасширит его. Окончательное значение разрешения определяется выражениемthenметод решить.

обработка ошибок

Как указано выше,Promiseзаключается в асинхронной обработке ошибок, что означает, что моя ошибка будет в следующемPromiseможно отловить, что в большинстве случаев хорошо, но есть проблема: а что, если код, отлавливающий ошибку, появится снова?
Мой подход обычно заключается в добавлении в конец кодаcatch:

let p1 = new Promise(function(reslove, reject){
    ajax('xxxxx')
})

p1
    .then(fullfilled, rejected)
    .then(fullfilled, rejected)
    .catch(function(e){
        //处理错误
    })

конец

На этом статья подходит к концу, если вы ее прочитали и что-то подумали, то я рад.
Буду продолжать обновлять дальшеPromise+generator, асинхронные функции и т. д.PromiseСоответствующие знания, желающие добиться прогресса вместе.