Использование и понимание промисов ES6

Node.js внешний интерфейс JavaScript Promise
Использование и понимание промисов ES6

JS асинхронный

Среда выполнения языка JS является «однопоточной», что означает, что одновременно может выполняться только одна задача; если задач несколько, они должны быть поставлены в очередь, предыдущая задача завершается, выполняется следующая задача, и так далее. Преимущество этого режима в том, что его относительно просто реализовать, а среда выполнения относительно проста; недостаток в том, что пока одна задача занимает много времени, последующие задачи должны быть поставлены в очередь, что задержит выполнение задачи. всю программу. Обычные браузеры не отвечают (притворная смерть), часто потому, что определенный фрагмент кода Javascript выполняется в течение длительного времени (например, бесконечный цикл), в результате чего вся страница зависает на этом месте, и другие задачи не могут быть выполнены.

Для решения этой проблемы язык Javascript разделяет режимы выполнения задач на два типа: синхронный и асинхронный.

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

«Асинхронный режим» очень важен. На стороне браузера операции, которые занимают много времени, должны выполняться асинхронно, чтобы избежать зависания браузера.Лучший пример — операции Ajax. На стороне сервера «асинхронный режим» является даже единственным режимом, потому что среда выполнения является однопоточной, и если разрешить синхронное выполнение всех http-запросов, производительность сервера резко упадет и быстро перестанет отвечать.

Общие шаблоны асинхронного программирования

  1. Перезвоните
    То есть f1 и f2 — это две функции, и f2 нужно дождаться выполнения результата выполнения f1, то есть f1(f2)
  2. событийный подход
    f1.on('done', f2); (запись JQ, когда f1 завершается, trigger("done") выполняет f2)
  3. Публикация — режим подписки
  4. Реализация объекта обещания

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

В этой статье основное внимание уделяется определению и использованию объектов ES6 Promise.Подробное объяснение ES6 от Ruan Yifeng — Promise ObjectЯ считаю, что все более или менее изучили руководство по ES6 от Учителя Руана в процессе изучения ES6, поэтому вот несколько примеров, описывающих характеристики и использование объектов Promise.

Основное использование

ES6 предоставляет конструктор Promise. Мы создаем экземпляр Promise. Конструктор Promise получает функцию в качестве параметра. Входящая функция имеет два параметра, которые являются двумя функциями.resolveа такжеrejectЭффект,resolveИзмените состояние промиса с неудачного на успешное и передайте результат асинхронной операции в качестве параметра; аналогичноrejectЗатем измените состояние с «не сбой» на «сбой», вызовите при сбое асинхронной операции и передайте ошибку, о которой сообщает асинхронная операция, в качестве параметра.
После создания экземпляра вы можете использоватьthenМетод указывает функцию обратного вызова успеха или неудачи соответственно.По сравнению с методом записи вложенной функции обратного вызова f1 (f2 (f3)), метод записи цепного вызова более красивый и читабельный.

let promise = new Promise((resolve, reject)=>{
    reject("拒绝了");
});
promise.then((data)=>{
    console.log('success' + data);
}, (error)=>{
    console.log(error)
});

执行结果:"拒绝了"

Особенности промисов

  • На объект не влияет внешний мир, начальное состояние pending (ожидание), состояние результата resolve и reject, это состояние определяет только результат асинхронной операции
  • Состояние может быть изменено только с Pending на одно из двух других, и это изменение необратимо и не может быть изменено снова.
    Это ожидает -> разрешено или ожидает -> отклонить
let promise = new Promise((resolve, reject)=>{
    reject("拒绝了");
    resolve("又通过了");
});
promise.then((data)=>{
    console.log('success' + data);
}, (error)=>{
    console.log(error)
});

执行结果: "拒绝了"

Приведенный выше код больше не будет выполнять метод разрешения

Правила метода then

  • thenСледующий ввод метода требует предыдущего вывода
  • Если обещание возвращается после того, как выполнение обещания все еще остается обещанием, результат выполнения обещания будет передан в следующий раз.thenсередина
  • еслиthenВозвращается не объект Promise, а обычное значение, и этот результат будет использоваться как успешный результат следующего затем
  • если текущийthenЕсли вы потерпите неудачу в середине, вы перейдете к следующему.thenнеудача
  • Если возврат не определен, он перейдет к следующему успеху независимо от того, является ли текущий успех или неудача.
  • catch - единственный выход, если ошибка не обработана
  • thenЕсли не написать в нем метод, то значение проникнет и перейдет в следующийthenсередина

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

let fs = require('fs');

function read(file, encoding) {
    return new Promise((resolve, reject)=>{
        fs.readFile(filePath, encodeing, (err, data)=> {
            if (err) reject(err);
            resolve(data);
        });
    })
}

Поскольку мы хотим видеть несколько последовательных обратных вызовов, мы специально создали 3 txt-файла, в которых содержимое файла 1 является именем файла 2, содержимое файла 2 является именем файла 3, а окончательное содержимое отображается под номером 3

Код выполнения следующий:

read('1.promise/readme.txt', 'utf8').then((data)=>{
    console.log(data)
});

Результат чтения файла readme2.txt.
Мы преобразуем этот код, добавляем несколько обратных вызовов и возвращаем текущий возвращаемый объект промиса во всех thens до последнего.

read('readme.txt', 'utf8').then((data)=>{
    return read(data, 'utf8');
}).then((data)=>{
    return read(data, 'utf8')
}).then((data)=>{
    console.log(data);
});

最终输出 readme3.txt的内容

Затем выполняем новую обработку на следующем шаге, мы обрабатываем содержимое readme3.txt и возвращаем

read('readme.txt', 'utf8').then((data)=>{
    return read(data, 'utf8');
}).then((data)=>{
    return read(data, 'utf8')
}).then(data=>{
    return data.split('').reverse().join(); // 这一步返回的是一个普通值,普通值在下一个then会作为resolve处理
}).then(null,data=>{   // 特意不对成功做处理,放过这一个值,进入下一步
    throw new Error('出错')  // 由于上一步返回的是普通值,走成功回调,不会走到这里
}).then(data=>{
    console.log(data)  // 最终会打印出来上上步的普通值
});

Здесь после того, как мы обработаем содержимое, мы передаем обычное значение следующему then, а потому, что у следующего then нет успешного метода (null)

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

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

read('readme.txt', 'utf8').then((data)=>{
    return read(data, 'utf8');
}).then((data)=>{
    return read(data, 'utf8')
}).then(data=>{
    return data.split('').reverse().join();
}).then(null,data=>{
    throw new Error('出错')
}).then(data=>{
    return read(data, 'utf8')
}).then(null,(err)=>{
    console.log(err)
});

результат:

Promises A+ (Promises Aplus)

Спецификация Promises Aplus определяет принцип работы Promise, спецификацию исходного кода и т. д. С помощью этой спецификации мы можем реализовать библиотеку классов Promise на основе спецификации PromiseA+. Давайте покажем реализацию исходного кода.

/**
 * Promise 实现 遵循promise/A+规范
 * 官方站: https://promisesaplus.com/
 * Promise/A+规范译文:
 * https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4
 */

// promise 三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function Promise(excutor) {
    let self = this; // 缓存当前promise实例对象
    self.status = PENDING; // 初始状态
    self.value = undefined; // fulfilled状态时 返回的信息
    self.reason = undefined; // rejected状态时 拒绝的原因
    self.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
    self.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数

    function resolve(value) { // value成功态时接收的终值
        if(value instanceof Promise) {
            return value.then(resolve, reject);
        }

        // 为什么resolve 加setTimeout?
        // 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.
        // 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。

        setTimeout(() => {
            // 调用resolve 回调对应onFulfilled函数
            if (self.status === PENDING) {
                // 只能由pedning状态 => fulfilled状态 (避免调用多次resolve reject)
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilledCallbacks.forEach(cb => cb(self.value));
            }
        });
    }

    function reject(reason) { // reason为失败态时接收的原因
        setTimeout(() => {
            // 调用reject 回调对应onRejected函数
            if (self.status === PENDING) {
                // 只能由pedning状态 => rejected状态 (避免调用多次resolve reject)
                self.status = REJECTED;
                self.reason = reason;
                self.onRejectedCallbacks.forEach(cb => cb(self.reason));
            }
        });
    }

    // 捕获在excutor执行器中抛出的异常
    // new Promise((resolve, reject) => {
    //     throw new Error('error in excutor')
    // })
    try {
        excutor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}


В этой части кода мы оцениваем разрешение и отклонение, а затем конструируемthenметод

/**
 * [注册fulfilled状态/rejected状态对应的回调函数]
 * @param  {function} onFulfilled fulfilled状态时 执行的函数
 * @param  {function} onRejected  rejected状态时 执行的函数
 * @return {function} promise2  返回一个新的promise对象
 */
Promise.prototype.then = function (onFulfilled, onRejected) {
    // 成功和失败的回调 是可选参数
    
    // onFulfilled成功的回调 onRejected失败的回调
    let self = this;
    let promise2;
    // 需要每次调用then时都返回一个新的promise
    promise2 = new Promise((resolve, reject) => {
    // 成功态
        if (self.status === 'resolved') {
            setTimeout(()=>{
                try {
                    // 当执行成功回调的时候 可能会出现异常,那就用这个异常作为promise2的错误的结果
                    let x = onFulfilled(self.value);
                    //执行完当前成功回调后返回结果可能是promise
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        // 失败态
        if (self.status === 'rejected') {
            setTimeout(()=>{
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        if (self.status === 'pending') {
           // 等待态时,当一部调用resolve/reject时,将onFullfilled/onReject收集暂存到集合中
           self.onResolvedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
            self.onRejectedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
        }
    });
    return promise2
}
// 其中规范要求对回调中增加setTimeout处理

Видно, что и resolve, и reject имеют метод resolvePromise, который обрабатывает новые промисы и инкапсулирует их для достижения цели обработки различных ситуаций.

/**
 * 对resolve 进行改造增强 针对resolve中不同值情况 进行处理
 * @param  {promise} promise2 promise1.then方法返回的新的promise对象
 * @param  {[type]} x         promise1中onFulfilled的返回值
 * @param  {[type]} resolve   promise2的resolve方法
 * @param  {[type]} reject    promise2的reject方法
 */
function resolvePromise(promise2,x,resolve,reject){
    if(promise2 === x){ // 如果从onFullfilled中返回的x就是promise2,就会导致循环引用报错
        return reject(new TypeError('Chaining cycle'));
    }
    let called; // 声明避免多次使用
    // x类型判断 如果是对象或者函数
    if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
    // 判断是否是thenable对象
        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);
    }
}

Вышеупомянутое в основном реализует основные методы Promise.В соответствии с использованием Promise добавляются некоторые методы класса.

// 用于promise方法链时 捕获前面onFulfilled/onRejected抛出的异常
Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason);
    })
}
Promise.resolve = function(value){
    return new Promise((resolve,reject)=>{
        resolve(value);
    })
}
Promise.prototype.catch = function(onRejected){
    // 默认不写成功
    return this.then(null,onRejected);
};
/**
 * Promise.all Promise进行并行处理
 * 参数: promise对象组成的数组作为参数
 * 返回值: 返回一个Promise实例
 * 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。
 */
Promise.all = function(promises){
    return new Promise((resolve,reject)=>{
        let arr = [];
        let i = 0;
        function processData(index,data){
            arr[index] = data;
            if(++i == promises.length){
                resolve(arr);
            }
        }
        for(let i = 0;i<promises.length;i++){
            promises[i].then(data=>{ // data是成功的结果
                processData(i,data);
            },reject);
        }
    })
}
/**
 * Promise.race
 * 参数: 接收 promise对象组成的数组作为参数
 * 返回值: 返回一个Promise实例
 * 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快)
 */
Promise.race = function(promises){
    return new Promise((resolve,reject)=>{
        for(let i = 0;i<promises.length;i++){
            promises[i].then(resolve,reject);
        }
    })
}

Наконец, мы экспортируем метод

module.exports = Promise;

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

npm install promises-aplus-tests -g
promises-aplus-test 文件名

Плагин для проверки того, соответствует ли исходный код в спецификации Promisea +

Надеюсь, эта статья поможет вам, выше