js история асинхронной разработки и анализ принципа Promise

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

Об асинхронности

Так называемая "асинхронность" просто означает, что задача не выполняется непрерывно. Можно понять, что задача искусственно разделена на две части, сначала выполняется первая часть, а затем выполняются другие задачи. Когда вы будете готовы, вернуться и выполнить первый раздел Второй пункт.

Например, задача — прочитать файл для обработки.Первая часть задачи — отправить запрос в операционную систему на чтение файла. Затем программа выполняет другие задачи, ждет, пока операционная система вернет файл, а затем переходит ко второй части задачи (обработке файла). Такое прерывистое выполнение называется асинхронным.

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

Проще говоря, синхронный означает, что все работают в очереди, а асинхронный означает, что все работают одновременно. Если вы не понимаете, что такое асинхронность и синхронность, прочтите дополнительные статьи по основам JS.

Асинхронная история

1. Написание обратного звонка

CallBack означает «функция обратного вызова», то есть функция, запускающая выполнение после выполнения асинхронной операции, например:

$.get("http://api.xxxx.com/xxx",callback);

Функция обратного вызова запускается, когда запрос завершен.

Обратные вызовы могут выполнять асинхронные операции, но те, кто пережил эпоху JQuery, должны были мучиться с определенным требованием, например, проект требует, чтобы фронтенд ajax запрашивал имя типа списка интерфейсов бэкенда, а затем использовал тип имя ajax для запроса идентификатора списка, в поле «Использовать идентификатор» для запроса определенного содержимого списка окончательный код, вероятно, будет таким

$.ajax({
    url: "type",
    data:1,
    success: function (a) {
        $.ajax({
            url: "list",
            data:a,
            success: function (b) {
                $.ajax({
                    url: "content",
                    data:b,
                    success: function (c) {
                        console.log(c)
                    }
                })
            }
        })
    }
})

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

2.Promise

Чтобы решить проблему «ада обратных вызовов», был предложен объект Promise, который позже был добавлен в стандарт ES6.Объект Promise — это просто контейнер, содержащий результат события (обычно асинхронной операции), которое завершится в будущем. . Синтаксически Promise — это объект, из которого можно получить сообщение для асинхронной операции. Обещания предоставляют унифицированный API, и различные асинхронные операции могут обрабатываться одинаково.

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

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

Функция разрешения состоит в том, чтобы изменить состояние объекта Promise с «незавершенного» на «успешное» (то есть с ожидающего на решенное), вызвать его при успешном выполнении асинхронной операции и передать результат асинхронная операция в качестве параметра; передать ошибку, сообщенную асинхронной операцией, в качестве параметра.

После создания экземпляра Promise метод then можно использовать для указания функции обратного вызова разрешенного состояния и отклоненного состояния соответственно.

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

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

Таким образом, Promise используется для решения проблемы «ада обратных вызовов», такой как постоянное чтение нескольких файлов, а метод записи следующий.

var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function (data) {
  console.log(data.toString());
})
.then(function () {
  return readFile(fileB);
})
.then(function (data) {
  console.log(data.toString());
})
.catch(function (err) {
  console.log(err);
});

Видно, что этот метод записи гораздо более интуитивен, чем метод записи CallBack. Но есть ли лучший способ написать это?

3. Генераторная функция

Функция-генератор отмечена *, а ключевое слово yield означает паузу. Разделите функцию на несколько частей, и вызов next продолжит выполнение вниз. Возвращаемый результат — итератор, а итератор имеет метод next.

function* read() {
    console.log(1);
    let a = yield '123';
    console.log(a);
    let b = yield 9
    console.log(b);
    return b;
}
let it = read();
console.log(it.next('213')); // {value:'123',done:false}
console.log(it.next('100')); // {value:9,done:false}
console.log(it.next('200')); // {value:200,done:true}
console.log(it.next('200')); // {value:200,done:true}

За доходностью следует значение value, а знаку yield equals предшествует значение, переданное нашим текущим вызовом next, а первое значение, переданное next, недействительно.

Генератор и обещание используются вместе при работе с асинхронностью.

let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);//将readFile转为Promise对象的实例
function* r() {
    let content1 = yield read('./2.promise/1.txt', 'utf8');
    let content2 = yield read(content1, 'utf8');
    return content2;
}

Это похоже на то, что мы хотим, но недостаточно просто написать так: если вы хотите получить результат r(), вам нужно обработать функцию

function co(it) {
    return new Promise(function (resolve, reject) {
        function next(d) {
            let { value, done } = it.next(d);
            if (!done) {
                value.then(function (data) { // 2,txt
                    next(data)
                }, reject)
            } else {
                resolve(value);
            }
        }
        next();
    });
}
co(r()).then(function (data) {
    console.log(data)//得到r()的执行结果
})

Этот метод обработки, очевидно, очень сложен, не то, что мы хотим, мы хотим написать его интуитивно, как синхронную функцию, и легко обрабатывать асинхронные. Есть ли такой способ?

4. функция асинхронного ожидания

Стандарт ES2017 представил асинхронные функции, чтобы сделать асинхронные операции более удобными.

Что такое асинхронная функция? Одним словом, это синтаксический сахар для Генераторных функций.

let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);

async function r(){
    try{
        let content1 = await read('./2.promise/100.txt','utf8');
        let content2 = await read(content1,'utf8');
        return content2;
    }catch(e){ // 如果出错会catch
        console.log('err',e)
    }
}

После сравнения вы обнаружите, что асинхронная функция заменяет звездочку (*) функции генератора на асинхронную, а доходность заменяет на ожидание, вот и все.

асинхронная функция возвращает обещание

r().then(function(data){
    console.log(data);
},function(err){

})

Пока функция async-await нас устраивает, будут ли в будущем решения получше? Я верю, что благодаря творчеству нашего обширного программного сообщества они будут.

Анализ принципа обещания

Функция async-await на самом деле просто синтаксический сахар для функции Generator, а реализация функции Generator тоже основана на Promise, поэтому разберем принцип реализации Promise.

Объекты промисов имеют следующие состояния:

  • pending: исходное состояние, ни выполнено, ни отклонено.
  • выполнено: Успешная операция.
  • отклонено: операция, которая не удалась.

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

function Promise(executor) { // executor是一个执行函数
    let self = this;
    self.status = 'pending';
    self.value = undefined; // 默认成功的值
    self.reason = undefined; // 默认失败的原因
    self.onResolvedCallbacks = []; // 存放then成功的回调
    self.onRejectedCallbacks = []; // 存放then失败的回调
    function resolve(value) { // 成功状态
        
    }
    function reject(reason) { // 失败状态
        
    }
    try {
        executor(resolve, reject)
    } catch (e) { // 捕获的时候发生异常,就直接失败了
        reject(e);
    }
}

Promise.prototype.then = function (onFulfilled, onRjected) {
//then方法
})

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

function resolve(value) { // 成功状态
        if (self.status === 'pending') {
            self.status = 'resolved';
            self.value = value;
            self.onResolvedCallbacks.forEach(function (fn) {
                fn();
            });
        }
    }

То же самое верно и для функции отклонения

function reject(reason) { // 失败状态
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function (fn) {
                fn();
            })
        }
    }

Затем мы завершаем функцию then

Promise.prototype.then = function (onFulfilled, onRjected) {
    let self = this;
    let promise2; //返回的promise
    if (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            
        })
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            
        })
    }
    // 当调用then时可能没成功 也没失败
    if (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {

        })
    }
    return promise2;
}

Промисы допускают связанные вызовы, поэтому возвращайте новый объект промиса promise2

Promise.prototype.then = function (onFulfilled, onRjected) {
    //成功和失败默认不穿给一个函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; //返回的promise
    if (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    // x可能是别人promise,写一个方法统一处理
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRjected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    
                    reject(e);
                }
            })

        })
    }
    // 当调用then时可能没成功 也没失败
    if (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            // 此时没有resolve 也没有reject
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}

Определите переменную x внутри promise2 как возвращаемое значение функции обратного вызова.Поскольку возвращаемое значение может иметь много возможных ситуаций, мы определяем функцию resolvePromise для унифицированной обработки

Возвращаемое значение можно разделить на

  • обещание возвращает себя (циклическая ссылка на ошибку)
  • Вернуть объект обещания (вызвать функцию обратного вызова успеха или отказа в соответствии с объектом обещания)
  • Возвращает нормальное значение (вызовите успешную функцию обратного вызова, чтобы передать возвращаемое значение)
  • Ошибка (вызов не возвращается к входящей ошибке)
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { 
        return reject(new TypeError('循环引用了'))
    }
    // 判定x是不是一个promise,promise应该是一个对象
    let called; // 表示是否调用过成功或者失败
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try { // {then:1}
            let then = x.then;
            if (typeof then === 'function') {
                // 成功
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    // y可能还是一个promise,在去解析直到返回的是一个普通值
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失败
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { // 说明是一个普通值
        resolve(x); // 调用成功回调
    }
}

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

Наконец, добавьте метод catch, очень простой

Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}

Это принципы основной функции обещания с полным кодом.

function Promise(executor) { // executor是一个执行函数
    let self = this;
    self.status = 'pending';
    self.value = undefined; // 默认成功的值
    self.reason = undefined; // 默认失败的原因
    self.onResolvedCallbacks = []; // 存放then成功的回调
    self.onRejectedCallbacks = []; // 存放then失败的回调
    function resolve(value) { // 成功状态
        if (self.status === 'pending') {
            self.status = 'resolved';
            self.value = value;
            self.onResolvedCallbacks.forEach(function (fn) {
                fn();
            });
        }
    }
    function reject(reason) { // 失败状态
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function (fn) {
                fn();
            })
        }
    }
    try {
        executor(resolve, reject)
    } catch (e) { 
        reject(e);
    }
}
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { 
        return reject(new TypeError('循环引用了'))
    }
    let called; 
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try { 
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    resolvePromise(promise2, y, resolve, reject)
                }, function (err) { //失败
                    if (called) return
                    called = true
                    reject(err);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true;
            reject(e);
        }
    } else { 
        resolve(x); 
    }
}
Promise.prototype.then = function (onFulfilled, onRjected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; 
    if (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        })
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    let x = onRjected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    
                    reject(e);
                }
            })

        })
    }

    if (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            });
        })
    }
    return promise2;
}

Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}