Научит вас реализовывать Promise в соответствии со спецификацией PromiseA+

внешний интерфейс JavaScript Promise ECMAScript 6
Научит вас реализовывать Promise в соответствии со спецификацией PromiseA+

Реализация соответствуетобещаниеA+ спецификацияразобраться При использовании промисов вам нужно сначала создать экземпляр, поэтому нам нужно создать конструктор промисов.

let p = new Promise(function(resolve, reject){
    // 先调谁,就走谁
    reject('不对了');  // 失败
    resolve(1);  // 成功  

    // 如果抛出异常也会走到then的失败函数里
    throw new Error('错了'); 
});
p.then(function(data) {
    console.log(data);    
}, function(err) {
    console.log(err);      // '不对了'
});

Новый экземпляр Promise содержит функцию выполнения (executor), которая имеет два параметра: методы успеха и неудачи.

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

условие

Все мы знаем, что промисы имеют три значения состояния.

  • ожидание (ожидание) состояние по умолчанию
  • выполнено (успех)
  • отклонено (не удалось)

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

// 实现promise
function Promise(executor) {
	const self = this;
	self.status = 'pending';	// 默认的状态,pending->fulfilled, pending->rejected
	self.value = null;		// 成功的值
	self.reason = null;		// 失败的原因

	function resolve(value) {	// 成功
	    if (self.status === 'pending') {
		self.status = 'fulfilled';
		self.value = value;
	    }
	}
	function reject(reason) {		// 失败
	    if (self.status === 'pending') {
		self.status = 'rejected';
		self.reason = reason;
            }
	}


	try {
	    executor(resolve, reject);
	} catch(e) {		// 如果抛出错误,就直接走失败的方法
	    reject(e);
	}
}

Promise.prototype.then = function(onFulfilled, onRejected) {
	const self = this;
	if (self.status === 'fulfilled') {
	    onFulfilled();
	}
	if (self.status === 'rejected') {
	    onRejected();
	}
};

module.exports = Promise;

Выше было написано много, но оказалось, что все они имеют дело с синхронизацией. Что делать, когда мы сталкиваемся с асинхронностью? Продолжим писать.

Асинхронные обещания

Асинхронный код может быть написан в обещании, и может быть выполнено несколько операций.

let p = new Promise(function(resolve, reject){
    // 异步操作
    setTimeout(function() {
        resolve('ok');
    }, 1000);
}).then(function(data) {
    console.log(data);    // 'ok'
    return `下面的人接着  + ${data}`;
}, function(err) {
    console.log(err);     
}).then(function(data) {
    console.log(data);    // '下面的人接着 ok'
}, function(err) {
    console.log(err);     
});
Реализовать асинхронное обещание

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

function Promise() {
    self.status = 'pending';
    self.value = null;
    self.reason = null;
+   self.onFulfilledCb = [];    // 存放then成功的回调
+   self.onRejectedCb = [];     // 存放then失败的回调   

    function resolve(value) {
        if (self.status === 'pending') {
            self.status = 'fulfilled';
            self.value = value;
     +      self.onFulfilledCb.forEach(function (fn) {
                fn();
            });
        }
    }
    
    function reject(reason) {
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
      +     self.onRejectedCb.forEach(function (fn) {
                fn();
            });
        }
    }  

    try {
        executor(resolve, reject);
    } catch(e) {        // 如果抛出错误,就直接走失败的方法
        reject(e);
    }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    if (self.status === 'fulfilled') {
        onFulfilled(self.value);
    } 
    if (self.status === 'rejected') {
        onRejected(self.reason);
    }
    // 当调用then时 可能没成功也没失败,就处于pending状态

+   if (self.status === 'pending') {
        // 将成功的回调添加到数组中
        self.onFulfilledCb.push(function () {
            onFulfilled(self.value);
        });
        self.onRejectedCb.push(function () {
            onRejected(self.reason);
        });
    }
};

module.exports = Promise;

цепной вызов

Приведенный выше код реализует то, как метод then обрабатывает асинхронный вызов.

Затем наступает важная часть: всем известно, что метод promise также поддерживает вызовы цепочки.

Однако связанный вызов then может быть реализован не так, как jquery возвращает это

☆️ Возвращает новое обещание

См. код ниже

let p = new Promise(function(resolve, reject) {
        resolve('ok');
});
p.then(function (data) {
    console.log(data);
    return data;
}, function (err) {
    console.log(err);
    // return '有返回值就走成功态'
}).then(function (data) {
    console.log('2 '+data);
}, function (err) {
    console.log(err);
})

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

Promise.prototype.then = function (onFulfilled, onRejected) {
    const self = this;
 +  let promise2;    // 定义一个promise2变量

    if (self.status === 'fulfilled') {
         // 返回一个新的promise
 +      promise2 = new Promise(function (resolve, reject) {
            // x可能是个普通值,也可能是个promise
 +          let x = onFulfilled(self.value);
            // x也可能是别人写的promise,写一个函数统一去处理
 +          resolvePromise(promise2, x, resolve, reject);
        });
    }
    if (self.status === 'rejected') {
 +       promise2 = new Promise(function (resolve, reject) {
 +          let x = onRejected(self.reason);
 +          resolvePromise(promise2, x, resolve, reject);
        });
    }
    // 当调用then时 可能没成功也没失败,就处于pending状态
    if (self.status === 'pending') {
        // 将成功的回调添加到数组中
+        promise2 = new Promise(function (resolve, reject) {
            self.onFulfilledCb.push(function () {
+               let x = onFulfilled(self.value);
+               resolvePromise(promise2, x, resolve, reject);
            });

            self.onRejectedCb.push(function () {
+               let x = onRejected(self.reason);
+               resolvePromise(promise2, x, resolve, reject);
            });
        });
    }

    return promise2;
};
function resolvePromise(p2, x, resolve, reject) {
    if (p2 === x) {    // 不能返回自己
        return reject(new TypeError('循环引用'));
    }
    let called;     // 表示是否调用成功or失败
    // x返回的可能是对象和函数也可能是一个普通的值
    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;
                    // y可能还是个promise,所以递归继续解析直到返回一个普通值
                    resolvePromise(p2, y, resolve, reject);
                }, function (e) {
                    if (called) return;
                    called = true;
                    reject(e);
                });
            } else {    
                // 处理then不是函数的情况,如{then: 1},就直接返回成功
                resolve(x);
            }
        } catch(e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);     // 返回一个普通值
    }
}

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

  • Если есть возвращаемое значение либо в успешном обратном вызове, либо в неудачном обратном вызове, тогда оно перейдет к успеху следующего, а затем
  • Если первый промис возвращает нормальное значение, он войдет в успешный обратный вызов следующего then; если первый промис вернет новый промис, вам нужно дождаться, пока результат возвращенного промиса будет передан следующему then.
  • resolvePromise, возвращаемый результат такой же, как и обещание, он не будет ни успешным, ни неудачным, мы сообщим об ошибке типа, чтобы подсказать

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

let p = new Promise(function (resolve, reject) {
    reject(999);
});
p.then().then().then(function (data) {
    console.log(data);  // 999    
}, function (err) {
    console.log(err);
});

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

Promise.prototype.then = function(onFulfilled, onRejected) {
+    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value
    };
+    onRejected = typeof onRejected === 'function' ? onFulfilled : function (err) {
        throw err
    };  
    const self = this;
    let promise2;
};

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

Кроме того, спецификация промиса требует, чтобы все onFulfilled и onRejected выполнялись асинхронно, поэтому нам нужно добавить к ним асинхронность.Очевидно, что это проще реализовать. См. код ниже.

Promise.prototype.then = function(onFulfilled, onRejected) {
    if (self.status === 'fulfilled') {
        promise2 = new Promise(function (resolve, reject) {
            // 当成功或失败执行时,有异常那么返回的promise应该处于失败态
            // 用try/catch来防止返回异常的情况
            // 加上setTimeout实现异步调用
+           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 = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
};
    if (self.status === 'pending') {
        // 将成功的回调添加到数组中
        promise2 = new Promise(function (resolve, reject) {
            self.onFulfilledCb.push(function () {
+               setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
            });
           
            self.onRejectedCb.push(function () {
+               setTimeout(function () {
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
        });
    }
    
    return promise2;

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

Впрочем, раз я тогда закончил писать, то напишу еще несколько, и пусть все вместе учатся и учатся.

Другие методы

метод catch для отлова ошибок

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

let p = new Promise(function(resolve, reject) {
    reject('错错错');
});
p.then(function(data){
    console.log(data);
}).catch(function(e){
    console.log(e);    // '错错错'
});

Код выше:

// 捕获错误的方法
Promise.prototype.catch = function(callback) {
     // 也是调用then方法,给成功的回调传一个null,给失败的回调传入callback
    return this.then(null, callback); 
};

Сам класс Promise также имеет некоторые методы, обычно используемые: all, race, resolve, reject.

Здесь все написано, тогда продолжайте писать, не говорите чушь, начнем! ! !

все методы

Использование Promise.all()

let p = new Promise(function(resolve, reject) {
   setTimeout(function() {
        resolve('北京');
    }, 1000);
});
let p2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve('南京');
    }, 200);
});
let p3 = new Promise(function(resolve, reject) {
    resolve('东京');
});
Promise.all([p, p2, p3]).then(function(data) {
    // all方法是所有的promise都成功后才会成功
    // 按照传入all里的顺序依次执行,p里面的定时器只是执行完成的时间而已,不影响all里输出顺序
    // 如:p这个需要等1秒后才会返回成功态,p2就需要等待1秒时间
    console.log(data);    // [ '北京', '南京', '东京' ]
});

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

Promise.all = function(items) {
    return new Promise(function(resolve, reject) {
        let res = [];    // 用来存储成功函数返回的值
        let num = 0;  // 记录都返回成功的数字
        let len = items.length;  // 数组的长度 
        // 对数组进行遍历
        for (let i = 0; i < len; i++) {
              items[i].then(function(data) {
                  res[i] = data;
                  if (++num === len) {
                      resolve(res);
                  }
              }, reject);
        }
    });
};

Так-то, так-то и так-то, все достигнуто

Приложите настойчивые усилия и есть метод гонки

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

метод гонки

Давайте посмотрим, как использовать

let p = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('北京');
    }, 1000);
});
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('南京');
    }, 500);
});
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('东京');
    });
});
Promise.race([p, p2, p3]).then(function (data) {
    // p3最先执行完返回成功,所以data就为p3的值,其他的p,p2都不返回
    console.log(data);  // '东京'
});

Итак, без лишних слов, давайте напишем

Promise.race = function(items) {
    return new Promise(function(resolve, reject) {
        for (let i = 0; i < items.length; i++) {
            items[i].then(resolve, reject);
        }
    });
};

В приведенном выше примере реализован метод гонки, затем напишите разрешение и отклонение за один раз.

Эти два метода кажутся очень простыми, потому что предыдущее предвидение уже очень хорошо и в основном относится к состоянию непосредственного использования. Давайте сначала посмотрим, как его использовать

разрешать и отклонять методы
// 成功
Promise.resolve([1,2,3,4]).then(function(data) {
    console.log(data);  // [1,2,3,4]
});
// 失败
Promise.reject('err!').then(function () {}, function (err) {
    console.log(err);   // 'err!'
});

Давай, поторопись, начинай писать, не стесняйся

Promise.resolve = function(value) {
    return new Promise(function(resolve, reject) {
        resolve(value);
    });
};
Promise.reject = function (reason) {
    return new Promise(function (resolve, reject) {
        reject(reason);
    });
};

Заканчивать! ! !

Дамы и господа, не чувствуете ли вы, что ваше тело опустошено?После того, как вы так много написали, это, наконец, закончилось, так что вы можете прийти и разобраться. Неправильно, и последний метод не был написан, это defer

Какова роль метода отсрочки?

Во-первых, для генерации экземпляра промиса не нужно писать new.

Во-вторых, потому что тестовая библиотека promises-aplus-tests of promises должна написать такой метод, хахаха

Давайте посмотрим, как его использовать, по сути, это то же самое, что и Q

function read() {
    let defer = Promise.defer();  // Q里写的是Q.defer()
    require('fs').readFile('./2.promise/1.txt', 'utf8', function (err, data) {
        if(err) defer.reject(err);
        defer.resolve(data);
    })
    return defer.promise;
}
read().then(function (data) {
    console.log(data)
},function(err){
    console.log(err);
})

Для нужд тестовой библиотеки реализуем последний

Promise.defer = Promise.deferred = function() {
    let def = {};
    def.promise = new Promise(function(resolve, reject) {
        def.resolve = resolve;
        def.reject = reject;
    });

    return def;
}

Наконец, все реализации завершены. Это нелегко, и спасибо за упорство в чтении, и я надеюсь, что вы извлекли из этого что-то полезное! Ха-ха

  • Наконец, когда приглашенные офицеры тоже пытаются выполнять обещания
  • Не забудьте протестировать код, который вы написали
  • Спецификация promiseA+ также имеет специальную библиотеку тестирования для запуска реализуемых вами промисов.
    • Установить глобально npm i promises-aplus-tests -g
    • обещания-aplus-тесты проекта.js
  • Если бег полностью √, то поздравляю, у вас появился еще один навык