Обещание должно находиться в одном из следующих трех состояний: Ожидание, Выполнено и Отклонено. Как только обещание разрешено или отклонено, оно не может перейти в какое-либо другое состояние (т. е. в неизменное состояние).
Основной процесс:
- Инициализировать состояние обещания (в ожидании)
- Немедленно выполните функцию fn, переданную в промисе, передайте функции разрешения и отклонения внутри промиса в качестве параметров для fn и обработайте их в соответствии с синхронизацией механизма событий.
- Выполните then(..) для регистрации массива обработки обратного вызова (тогда метод может вызываться несколько раз одним и тем же промисом)
- Ключевым моментом в Promise является обеспечение того, чтобы параметры onFulfilled и onRejected, переданные методом then, должны выполняться в новом стеке выполнения после цикла обработки событий, в котором вызывается метод then.
Настоящее связанное обещание означает, что после того, как текущее обещание достигает выполненного состояния, начинается следующее обещание.
цепной вызов
Давайте сначала посмотрим на результат выполнения Promise, там есть следующий фрагмент кода:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
resolve({ test: 2 })
reject({ test: 2 })
}, 1000)
}).then((data) => {
console.log('result1', data)
},(data1)=>{
console.log('result2',data1)
}).then((data) => {
console.log('result3', data)
})
//result1 { test: 1 }
//result3 undefined
Очевидно, что здесь выводятся другие данные. Из этого можно увидеть несколько вещей:
- Можно выполнять связанные вызовы, и каждый раз затем возвращать новое обещание (результаты двух распечаток несовместимы, если это один и тот же экземпляр, результаты распечатки должны быть согласованы.
- Выводится только содержимое первого resolve, а содержимое reject не выводится, то есть Promise stateful и состояние может быть только pending -> выполнено или pending-> reject, что необратимо.
- Затем возвращается новый промис, но зарегистрированный в нем обратный вызов по-прежнему принадлежит предыдущему промису.
Основываясь на вышеизложенных пунктах, сначала напишемPromiseA+Модель Canonical Promise с единственным методом разрешения:
function Promise(fn){
let state = 'pending';
let value = null;
const callbacks = [];
this.then = function (onFulfilled){
return new Promise((resolve, reject)=>{
handle({ //桥梁,将新 Promise 的 resolve 方法,放到前一个 promise 的回调对象中
onFulfilled,
resolve
})
})
}
function handle(callback){
if(state === 'pending'){
callbacks.push(callback)
return;
}
if(state === 'fulfilled'){
if(!callback.onFulfilled){
callback.resolve(value)
return;
}
const ret = callback.onFulfilled(value) //处理回调
callback.resolve(ret) //处理下一个 promise 的resolve
}
}
function resolve(newValue){
const fn = ()=>{
if(state !== 'pending')return
state = 'fulfilled';
value = newValue
handelCb()
}
setTimeout(fn,0) //基于 PromiseA+ 规范
}
function handelCb(){
while(callbacks.length) {
const fulfiledFn = callbacks.shift();
handle(fulfiledFn);
};
}
fn(resolve)
}
Эта модель проста и понятна.Ключевым моментом здесь является вновь созданный промис.Узел, состояние которого становится выполненным, — это когда выполняется обратный вызов предыдущего промиса. То есть, когда состояние промиса выполнено, его функция обратного вызова будет выполнена, а результат, возвращенный функцией обратного вызова, будет использоваться в качестве значения и возвращен следующему промису (то есть промису, сгенерированному в то время). ), и состояние следующего промиса тоже меняется (выполняется resolve или reject), а затем выполняется его callback, и так далее... выходит эффект цепочки вызовов.
Но если это именно так в примере, мы можем написать:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
console.log('result3')
})
//result1 { test: 1 }
//result3
На самом деле наши часто используемые цепные вызовы используются в асинхронных обратных вызовах, чтобы решить проблему «ада обратных вызовов». Следующий пример:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).then((data) => {
console.log('result2', data)
})
function test(id) {
return new Promise(((resolve) => {
setTimeout(() => {
resolve({ test: 2 })
}, 5000)
}))
}
//基于第一个 Promise 模型,执行后的输出
//result1 { test: 1 }
//result2 Promise {then: ƒ}
Используя описанную выше модель Promise, мы получаем явно не тот результат, который нам нужен. Если внимательно посмотреть на приведенную выше модель, то при выполнении callback.resolve входящим параметром является возврат callback.onFulfilled Очевидно, что этот тестовый пример возвращает Promise, а метод разрешения в нашей модели Promise не имеет специальной обработки. Затем мы изменим разрешение на:
function Promise(fn){
...
function resolve(newValue){
const fn = ()=>{
if(state !== 'pending')return
if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
const {then} = newValue
if(typeof then === 'function'){
// newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
//相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
then.call(newValue,resolve)
return
}
}
state = 'fulfilled';
value = newValue
handelCb()
}
setTimeout(fn,0)
}
...
}
Используя эту модель и протестировав наш пример, мы получаем правильный результат:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).then((data) => {
console.log('result2', data)
})
function test(id) {
return new Promise(((resolve, reject) => {
setTimeout(() => {
resolve({ test: 2 })
}, 5000)
}))
}
//result1 { test: 1 }
//result2 { test: 2 }
Очевидно, что добавленная логика предназначена для обработки, когда входным параметром разрешения является Promise. Давайте посмотрим, как обещание, созданное в тесте, не вызывает метод then. Из приведенного выше анализа мы уже знаем, что функция обратного вызова Promise регистрируется путем вызова ее метода then, поэтому функция обратного вызова Promise, созданная в тесте, пуста.
Очевидно, что если нет функции обратного вызова, то при выполнении разрешения нет возможности связать ее в цепочку. Поэтому нам нужно активно внедрить для него функцию обратного вызова.
Нам нужно только отложить выполнение функции разрешения промиса, сгенерированного в первом, а затем до тех пор, пока состояние промиса в тесте не будет onFulfilled, тогда цепочка может продолжаться. Поэтому, когда входным параметром разрешения является Promise, вызовите его метод then, чтобы внедрить для него функцию обратного вызова, а внедрение — это метод разрешения предыдущего промиса, поэтому используйте call для привязки точки this.
На основе новой модели Promise экземпляр Promise и его функция обратного вызова, сгенерированные вышеприведенным процессом выполнения, можно увидеть в следующей таблице:
Promise | callback |
---|---|
P1 | [{onFulfilled:c1(fn в первом, затем), resolve:p2resolve}] |
P2 (генерируется, когда затем звонит P1) | [{onFulfilled:c2(fn во втором, затем), resolve:p3resolve}] |
P3 (генерируется, когда затем звонит P2) | [] |
P4 (выполните c1, чтобы сгенерировать [вызов теста]) | [{onFulfilled:p2resolve,resolve:p5resolve}] |
P5 (генерируется в логике then.call при вызове p2resolve) | [] |
С помощью этой таблицы мы можем четко знать, что порядок выполнения обратного вызова в каждом экземпляре следующий:
c1 -> p2resolve -> c2 -> p3resolve -> [] -> p5resolve -> []
Вышеизложенный принцип цепного вызова.
reject
Давайте снова завершим логику отклонения. Вам нужно только добавить логику отклонения при регистрации обратных вызовов и изменений состояния.
Полный код выглядит следующим образом:
function Promise(fn){
let state = 'pending';
let value = null;
const callbacks = [];
this.then = function (onFulfilled,onRejected){
return new Promise((resolve, reject)=>{
handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
function handle(callback){
if(state === 'pending'){
callbacks.push(callback)
return;
}
const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
const next = state === 'fulfilled'? callback.resolve:callback.reject;
if(!cb){
next(value)
return;
}
const ret = cb(value)
next(ret)
}
function resolve(newValue){
const fn = ()=>{
if(state !== 'pending')return
if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
const {then} = newValue
if(typeof then === 'function'){
// newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
//相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
then.call(newValue,resolve, reject)
return
}
}
state = 'fulfilled';
value = newValue
handelCb()
}
setTimeout(fn,0)
}
function reject(error){
const fn = ()=>{
if(state !== 'pending')return
if(error && (typeof error === 'object' || typeof error === 'function')){
const {then} = error
if(typeof then === 'function'){
then.call(error,resolve, reject)
return
}
}
state = 'rejected';
value = error
handelCb()
}
setTimeout(fn,0)
}
function handelCb(){
while(callbacks.length) {
const fn = callbacks.shift();
handle(fn);
};
}
fn(resolve, reject)
}
Обработка исключений
Исключения обычно относятся к ошибкам, вызванным ошибками кода при выполнении обратных вызовов успеха/неудачи.Для таких исключений мы используем try-catch для перехвата ошибок и переводим Promise в отклоненное состояние.
Код дескриптора изменяется следующим образом:
function handle(callback){
if(state === 'pending'){
callbacks.push(callback)
return;
}
const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
const next = state === 'fulfilled'? callback.resolve:callback.reject;
if(!cb){
next(value)
return;
}
try {
const ret = cb(value)
next(ret)
} catch (e) {
callback.reject(e);
}
}
Когда мы на самом деле его используем, мы часто привыкли регистрировать методы catch для обработки ошибок, например:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).catch((ex) => {
console.log('error', ex)
})
На самом деле, неважно, ошибка это или исключение, в конечном итоге оно реализуется через отклонение. Другими словами, тогда это может быть обработано обратным вызовом ошибки. Таким образом, мы можем добавить метод catch следующим образом:
function Promise(fn){
...
this.then = function (onFulfilled,onRejected){
return new Promise((resolve, reject)=>{
handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
this.catch = function (onError){
this.then(null,onError)
}
...
}
Наконец метод
В практических приложениях мы легко можем столкнуться с таким сценарием, каким бы ни было конечное состояние промиса, необходимо выполнить какие-то конечные операции. Мы помещаем эти операции в finally, то есть функция, зарегистрированная в finally, не имеет ничего общего с состоянием Promise и не зависит от результата выполнения Promise. Таким образом, мы можем написать логику finally следующим образом:
function Promise(fn){
...
this.catch = function (onError){
this.then(null,onError)
}
this.finally = function (onDone){
this.then(onDone,onDone)
}
...
}
разрешать и отклонять методы
На практике мы можем использовать методы Promise.resolve и Promise.reject для переноса экземпляров, отличных от Promise, в экземпляры Promise. Следующий пример:
Promise.resolve({name:'winty'})
Promise.reject({name:'winty'})
// 等价于
new Promise(resolve => resolve({name:'winty'}))
new Promise((resolve,reject) => reject({name:'winty'}))
В этих случаях входные параметры Promise.resolve могут иметь следующие ситуации:
- Нет параметров [возвращает разрешенный объект Promise напрямую]
- Обычный объект данных [возвращает разрешенный объект Promise напрямую]
- Экземпляр Promise [возвращает текущий экземпляр напрямую]
- Объект thenable (объект thenable ссылается на объект с методом then) [превращает его в объект Promise и немедленно выполняет метод then объекта thenable. ]
Основываясь на вышеизложенном, мы можем реализовать метод Promise.resolve следующим образом:
function Promise(fn){
...
this.resolve = function (value){
if (value && value instanceof Promise) {
return value;
} else if (value && typeof value === 'object' && typeof value.then === 'function'){
let then = value.then;
return new Promise(resolve => {
then(resolve);
});
} else if (value) {
return new Promise(resolve => resolve(value));
} else {
return new Promise(resolve => resolve());
}
}
...
}
Promise.reject похож на Promise.resolve, разница в том, что Promise.reject всегда возвращает отклоненный экземпляр промиса с состоянием, и если параметр Promise.resolve является экземпляром промиса, он возвращает экземпляр промиса, соответствующий параметру, так что состояние не обязательно. Поэтому реализация reject намного проще, а именно:
function Promise(fn){
...
this.reject = function (value){
return new Promise(function(resolve, reject) {
reject(value);
});
}
...
}
Promise.all
Входным параметром является массив экземпляров Promise, затем регистрируется метод then, а затем метод then выполняется после изменения состояния экземпляров Promise в массиве на выполненное. В основном это логика подсчета. Всякий раз, когда состояние промиса становится выполненным, данные, возвращаемые экземпляром, сохраняются, а затем счетчик уменьшается на 1. Когда счетчик становится равным 0, это означает, что все экземпляры промиса в массиве имеют был казнен.
function Promise(fn){
...
this.all = function (arr){
var args = Array.prototype.slice.call(arr);
return new Promise(function(resolve, reject) {
if(args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
if(val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if(typeof then === 'function') {
then.call(val, function(val) {
res(i, val);
}, reject);
return;
}
}
args[i] = val;
if(--remaining === 0) {
resolve(args);
}
} catch(ex) {
reject(ex);
}
}
for(var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
}
...
}
Promise.race
Понимание Promise.all упрощает понимание Promise.race. Его входным параметром также является массив экземпляров Promise, а затем, когда состояние Promise в массиве становится выполненным, выполняется зарегистрированный им метод обратного вызова. Поскольку состояние Promise можно изменить только один раз, нам нужно только внедрить метод разрешения объекта Promise, сгенерированного в Promise.race, в функцию обратного вызова каждого экземпляра Promise в массиве.
function Promise(fn){
...
this.race = function(values) {
return new Promise(function(resolve, reject) {
for(var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
}
...
}
Суммировать
Исходный код Promise составляет всего несколько сотен строк, мы можем начать с результата выполнения, проанализировать процесс выполнения каждого шага, а затем подумать о его функции. Самый важный момент — понимать, что функция then отвечает за регистрацию обратных вызовов, а реальное выполнение — после изменения состояния промиса. И когда входным параметром Resolve является Promise, если вы хотите вызвать его в цепочке, вы должны вызвать его метод then (then.call) и внедрить метод Resolve предыдущего Promise в его массив обратного вызова.
Дополнительные инструкции
Хотя потом вообще считается микрозадачей. Однако браузер не может имитировать микрозадачи, в настоящее время используется setImmediate, это тоже макрозадача, и если она не совместима, все равно используется setTimeout. Кроме того, полифилл промисов (es6-promise) также использует setTimeout. Поэтому setTimeout используется здесь напрямую, чтобы заменить микрозадачи макрозадачами.
использованная литература
- Спецификация PromiseA+
- Уточнение принципа реализации промиса
- 30 минут, чтобы вы полностью поняли принцип Promise
Полная модель Обещания
function Promise(fn) {
let state = 'pending'
let value = null
const callbacks = []
this.then = function (onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
handle({
onFulfilled,
onRejected,
resolve,
reject,
})
})
}
this.catch = function (onError) {
return this.then(null, onError)
}
this.finally = function (onDone) {
this.then(onDone, onError)
}
this.resolve = function (value) {
if (value && value instanceof Promise) {
return value
} if (value && typeof value === 'object' && typeof value.then === 'function') {
const { then } = value
return new Promise((resolve) => {
then(resolve)
})
} if (value) {
return new Promise(resolve => resolve(value))
}
return new Promise(resolve => resolve())
}
this.reject = function (value) {
return new Promise(((resolve, reject) => {
reject(value)
}))
}
this.all = function (arr) {
const args = Array.prototype.slice.call(arr)
return new Promise(((resolve, reject) => {
if (args.length === 0) return resolve([])
let remaining = args.length
function res(i, val) {
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
const { then } = val
if (typeof then === 'function') {
then.call(val, (val) => {
res(i, val)
}, reject)
return
}
}
args[i] = val
if (--remaining === 0) {
resolve(args)
}
} catch (ex) {
reject(ex)
}
}
for (let i = 0; i < args.length; i++) {
res(i, args[i])
}
}))
}
this.race = function (values) {
return new Promise(((resolve, reject) => {
for (let i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject)
}
}))
}
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback)
return
}
const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected
const next = state === 'fulfilled' ? callback.resolve : callback.reject
if (!cb) {
next(value)
return
}
let ret;
try {
ret = cb(value)
} catch (e) {
callback.reject(e)
}
callback.resolve(ret);
}
function resolve(newValue) {
const fn = () => {
if (state !== 'pending') return
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
const { then } = newValue
if (typeof then === 'function') {
// newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
// 相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
then.call(newValue, resolve, reject)
return
}
}
state = 'fulfilled'
value = newValue
handelCb()
}
setTimeout(fn, 0)
}
function reject(error) {
const fn = () => {
if (state !== 'pending') return
if (error && (typeof error === 'object' || typeof error === 'function')) {
const { then } = error
if (typeof then === 'function') {
then.call(error, resolve, reject)
return
}
}
state = 'rejected'
value = error
handelCb()
}
setTimeout(fn, 0)
}
function handelCb() {
while (callbacks.length) {
const fn = callbacks.shift()
handle(fn)
}
}
try {
fn(resolve, reject)
} catch(ex) {
reject(ex);
}
}
Наконец
Если вы считаете, что контент полезен, вы можете обратить внимание на мой официальный аккаунт «Front-end Q» и учиться и расти вместе~~