为了更好地理解promises如何在Javascript中工作,我决定自己试一试代码基本实现。
基本上我想实现Promises Object(我在代码中称之为Aaa),它将函数作为参数。此函数可以调用解析为resolve
的承诺,或拒绝reject
它。基本实现和用法如下。根据承诺规范,不确定第二个参数是否可接受,但这是我到目前为止所得到的。
Aaa=function(f,pause) {
console.log("ggg");
var t=this;
this.f=f;
this.thens=[];
this.resolve=function(g) {
for(var i=0;i<t.thens.length;i++)
{
// try/catch to be used later for dealing with exceptions
try
{
t.thens[i].f(g);
t.thens[i].resolve();
}
catch(ex)
{}
}
};
// to be implemented later
this.reject=function(g) {};
this.then=function(resolve,reject) {
// i'm passing true for pause argument as we dont need to execute promise code just yet
var nextPromise=new Aaa(resolve,true);
this.thens.push(nextPromise);
return nextPromise;
}
if(!pause)
this.f(this.resolve,this.reject);
}
var aaa=new Aaa(function(resolve,reject) {
console.log("aaa");
setTimeout(function() {
console.log("fff");
resolve("good");
},2000);
console.log("bbb");
});
所以现在可以创建,调用和解决承诺。每个then
方法都会返回新的Aaa(Promise),因此可以链接这些方法。现在,下面的代码使用上面创建的promise和链then
回调。每个then
都会返回新的承诺,在这种情况下它似乎正常工作:
aaa.then(function(res) {
console.log("ccc");
console.log(res);
})
.then(function(res) {
console.log("ddd");
console.log(res);
},function(rej) {
console.log("eee");
console.log(rej);
});
我得到的输出是:
ggg
aaa
bbb
ggg
ggg
fff
ccc
good
ddd
undefined
但是当then
个调用之一返回一个promise:
aaa.then(function(res) {
console.log("ccc");
console.log(res);
// here we return the promise manually. then next then call where "ddd" is output should not be called UNTIL this promise is resolved. How to do that?
return new Aaa(function(resolve,reject) {
console.log("iii");
setTimeout(function() {
console.log("kkk");
resolve("good2");
// reject("bad");
},2000);
console.log("jjj");
}).then(function (res) {
console.log("lll");
console.log(res);
});
})
.then(function(res) {
console.log("ddd");
console.log(res);
},function(rej) {
console.log("eee");
console.log(rej);
});
输出结果为:
ggg
aaa
bbb
ggg
ggg
fff
ccc
good
ggg
iii
jjj
ggg
ddd
undefined
kkk
lll
good2
不应该调用输出ddd
的调用,直到我们刚刚添加的返回的promise被解析。
如何最好地实施?
答案 0 :(得分:79)
有很多案例你不在这里处理。最好的办法是首先将承诺建立为状态机:
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise() {
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value once FULFILLED or REJECTED
var value = null;
// store sucess & failure handlers
var handlers = [];
}
现在让我们定义一个简单的帮助器,用于我们的其余实现:
// a function that returns `then` if `value` is a promise, otherwise `null`
function getThen(value) {
if (result && (typeof result === 'object' || typeof result === 'function')) {
var then = value.then;
if (typeof then === 'function') {
return then;
}
}
return null;
}
接下来,我们需要考虑可能发生的每种转换:
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise() {
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value once FULFILLED or REJECTED
var value = null;
// store sucess & failure handlers
var handlers = [];
function resolve(result) {
try {
var then = getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject)
return
}
state = FULFILLED;
value = result;
} catch (e) {
reject(e);
}
}
function reject(error) {
state = REJECTED;
value = error;
}
}
注意resolve
如何接收Promise作为其参数,但Promise永远不能用另一个Promise来实现。所以我们必须处理这个特例。
另请注意,承诺只能被履行/拒绝一次。我们还有第三方承诺可能行为不端的问题,我们应该保护我们的代码。出于这个原因,我还没有从result.then(resolve, reject)
内拨打resolve
。相反,我把它分成一个单独的函数:
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*/
function doResolve(fn, onFulfilled, onRejected) {
var done = false;
try {
fn(function (value) {
if (done) return
done = true
onFulfilled(value)
}, function (reason) {
if (done) return
done = true
onRejected(reason)
})
} catch (ex) {
if (done) return
done = true
onRejected(ex)
}
}
所以现在我们有一个完整的状态机,但无法观察或触发状态的变化。让我们首先添加一种通过传入解析器函数来触发状态更改的方法。
function Promise(fn) {
if (typeof this !== 'object')
throw new TypeError('Promises must be constructed via new');
if (typeof fn !== 'function')
throw new TypeError('fn must be a function');
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value once FULFILLED or REJECTED
var value = null;
// store sucess & failure handlers
var handlers = [];
function resolve(result) {
try {
var then = getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject)
return
}
state = FULFILLED;
value = result;
} catch (e) {
reject(e);
}
}
function reject(error) {
state = REJECTED;
value = error;
}
doResolve(fn, resolve, reject);
}
正如您所看到的,我们重新使用doResolve
因为我们有另一个不受信任的解析器。 fn
可能会多次调用resolve
或reject
,这可能会引发错误。我们需要处理所有这些案例(以及doResolve
所做的事情。)
我们现在拥有已完成的状态机,但我们还没有公开任何有关状态的信息。让我们尝试添加.done(onFulfilled, onRejected)
方法,就像.then
一样,除了它不会返回Promise,也不会处理onFulfilled
和onRejected
引发的错误。
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise(fn) {
if (typeof this !== 'object')
throw new TypeError('Promises must be constructed via new');
if (typeof fn !== 'function')
throw new TypeError('fn must be a function');
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value once FULFILLED or REJECTED
var value = null;
// store sucess & failure handlers
var handlers = [];
function resolve(result) {
try {
var then = getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject)
return
}
state = FULFILLED;
value = result;
handlers.forEach(handle);
handlers = null;
} catch (e) {
reject(e);
}
}
function reject(error) {
state = REJECTED;
value = error;
handlers.forEach(handle);
handlers = null;
}
function handle(handler) {
if (state === PENDING) {
handlers.push(handler);
} else {
if (state === FULFILLED && typeof handler.onFulfilled === 'function') {
handler.onFulfilled(value);
}
if (state === REJECTED && typeof handler.onRejected === 'function') {
handler.onRejected(value);
}
}
}
this.done = function (onFulfilled, onRejected) {
setTimeout(function () { // ensure we are always asynchronous
handle({
onFulfilled: onFulfilled,
onRejected: onRejected
});
}, 0);
}
doResolve(fn, resolve, reject);
}
请注意我们必须处理在承诺被履行/拒绝之前和之后调用.done
的情况。
我们几乎有一个完整的promise实现,但是,正如您在构建自己的实现时已经注意到的那样,我们需要一个返回Promise的.then
方法。
我们可以从.done
this.then = function (onFulfilled, onRejected) {
var self = this;
return new Promise(function (resolve, reject) {
return self.done(function (result) {
if (typeof onFulfilled === 'function') {
try {
return resolve(onFulfilled(result));
} catch (ex) {
return reject(ex);
}
} else {
return resolve(result);
}
}, function (error) {
if (typeof onRejected === 'function') {
try {
return resolve(onRejected(error));
} catch (ex) {
return reject(ex);
}
} else {
return reject(error);
}
});
});
}
请注意我们现在如何免费获得您正在努力解决的问题,因为resolve
接受了Promise并等待它得到解决。
N.B。我还没有对此Promise实施进行过测试(尽管据我所知,这是正确的)。您应该测试针对Promises / A +测试套件(https://github.com/promises-aplus/promises-tests)构建的任何实现,并且还可以找到Promises / A +规范(https://github.com/promises-aplus/promises-spec),用于确定任何特定部分的正确行为算法。作为最终资源,promise是Promise规范的一个非常小的实现。
答案 1 :(得分:15)
(对于完整的Promise实现,向下滚动)。
有几个问题,但我认为你的代码中的主要错误是你接受then
方法的参数并将其作为参数传递给新的承诺:
this.then=function(resolve,reject) {
var nextPromise=new Aaa(resolve,true);
// ...
尽管两个参数都是回调函数,但它们具有不同的签名,并且用于完全不同的目的:
then
方法的(第一个)参数是一个回调函数,它只会在以后异步执行,当基本承诺被解析时,解析后的值作为参数传递。您还可以在代码中看到差异,将参数作为 f 属性存储在构造函数中。你有这两个:
t.thens[i].f(g);
...其中 g 是已解析的值,但也是:
this.f(this.resolve,this.reject);
......参数是函数。当您创建 nextPromise 时,实际上您将首先使用这两个参数调用 f ,然后使用 g 参数调用 f 。
我们可以按照Promises/A+ specification:
中的要求构建我们自己的Promise实现只允许2个状态转换:从挂起到完成,从挂起到拒绝。不可能进行其他转换,并且一旦执行转换,承诺值(或拒绝原因)不应更改。
这是一个符合上述限制的简单实现。注释参考了上述规范中的编号要求:
function MyPromise(executor) {
this.state = 'pending';
this.value = undefined;
executor(this.resolve.bind(this), this.reject.bind(this));
}
// 2.1.1.1: provide only two ways to transition
MyPromise.prototype.resolve = function (value) {
if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
this.state = 'fulfilled'; // 2.1.1.1: can transition
this.value = value; // 2.1.2.2: must have a value
}
MyPromise.prototype.reject = function (reason) {
if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
this.state = 'rejected'; // 2.1.1.1: can transition
this.value = reason; // 2.1.3.2: must have a reason
}
当然,这不提供then
方法,这是Promises的关键:
then
方法这是规范的核心。上面的代码可以扩展为公开then
方法,它返回一个promise并提供相应的then
回调的异步执行,只提供一次,提供多个then
调用,将异常转换为拒绝,等等。
因此,下面的代码添加了then
方法,还添加了一个单独定义的broadcast
函数,因为它必须在任何状态更改时调用:这不仅包括{的效果{1}}方法(将承诺添加到列表中),以及then
和resolve
方法(状态和值更改)。
reject
这涵盖了几乎所有内容,但在function MyPromise(executor) {
this.state = 'pending';
this.value = undefined;
// A list of "clients" that need to be notified when a state
// change event occurs. These event-consumers are the promises
// that are returned by the calls to the `then` method.
this.consumers = [];
executor(this.resolve.bind(this), this.reject.bind(this));
}
// 2.1.1.1: provide only two ways to transition
MyPromise.prototype.resolve = function (value) {
if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
this.state = 'fulfilled'; // 2.1.1.1: can transition
this.value = value; // 2.1.2.2: must have a value
this.broadcast();
}
MyPromise.prototype.reject = function (reason) {
if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
this.state = 'rejected'; // 2.1.1.1: can transition
this.value = reason; // 2.1.3.2: must have a reason
this.broadcast();
}
// A promise’s then method accepts two arguments:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
var consumer = new MyPromise(function () {});
// 2.2.1.1 ignore onFulfilled if not a function
consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
// 2.2.1.2 ignore onRejected if not a function
consumer.onRejected = typeof onRejected === 'function' ? onRejected : null;
// 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise
this.consumers.push(consumer);
// It might be that the promise was already resolved...
this.broadcast();
// 2.2.7: .then() must return a promise
return consumer;
};
MyPromise.prototype.broadcast = function() {
var promise = this;
// 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved
if (this.state === 'pending') return;
// 2.2.6.1, 2.2.6.2 all respective callbacks must execute
var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected';
var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject';
// 2.2.4 onFulfilled/onRejected must be called asynchronously
setTimeout(function() {
// 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once
promise.consumers.splice(0).forEach(function(consumer) {
try {
var callback = consumer[callbackName];
// 2.2.1.1, 2.2.1.2 ignore callback if not a function, else
// 2.2.5 call callback as plain function without context
if (callback) {
// TODO: 2.2.7.1. For now we simply fulfill the promise:
consumer.resolve(callback(promise.value));
} else {
// 2.2.7.3 resolve in same way as current promise
consumer[resolver](promise.value);
}
} catch (e) {
// 2.2.7.2
consumer.reject(e);
};
})
});
};
评论时,必须要求所谓的承诺解决程序:
这是一个以不同方式处理值(或者甚至是promises)的过程:而不是按原样返回值,过程将对该值执行TODO:
方法并异步地履行承诺从then
回调中收到的值。规范中没有提到它,但不仅在then
方法中执行,而且在使用这样的值解决主要承诺时,这很有意思。
因此现有的then
方法应该替换为“Promise Resolution Procedure”,它将调用原始方法。最初的一个可以称为“履行”,表明它将解决承诺总是满足:
resolve
现在这是Promises / A +兼容,至少它通过了测试套件。然而,Promise对象暴露了太多的方法和属性:
function MyPromise(executor) {
this.state = 'pending';
this.value = undefined;
// A list of "clients" that need to be notified when a state
// change event occurs. These event-consumers are the promises
// that are returned by the calls to the `then` method.
this.consumers = [];
executor(this.resolve.bind(this), this.reject.bind(this));
}
// 2.1.1.1: provide only two ways to transition
MyPromise.prototype.fulfill = function (value) {
if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
this.state = 'fulfilled'; // 2.1.1.1: can transition
this.value = value; // 2.1.2.2: must have a value
this.broadcast();
}
MyPromise.prototype.reject = function (reason) {
if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
this.state = 'rejected'; // 2.1.1.1: can transition
this.value = reason; // 2.1.3.2: must have a reason
this.broadcast();
}
// A promise’s then method accepts two arguments:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
var consumer = new MyPromise(function () {});
// 2.2.1.1 ignore onFulfilled if not a function
consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
// 2.2.1.2 ignore onRejected if not a function
consumer.onRejected = typeof onRejected === 'function' ? onRejected : null;
// 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise
this.consumers.push(consumer);
// It might be that the promise was already resolved...
this.broadcast();
// 2.2.7: .then() must return a promise
return consumer;
};
MyPromise.prototype.broadcast = function() {
var promise = this;
// 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved
if (this.state === 'pending') return;
// 2.2.6.1, 2.2.6.2 all respective callbacks must execute
var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected';
var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject';
// 2.2.4 onFulfilled/onRejected must be called asynchronously
setTimeout(function() {
// 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once
promise.consumers.splice(0).forEach(function(consumer) {
try {
var callback = consumer[callbackName];
// 2.2.1.1, 2.2.1.2 ignore callback if not a function, else
// 2.2.5 call callback as plain function without context
if (callback) {
// 2.2.7.1. execute the Promise Resolution Procedure:
consumer.resolve(callback(promise.value));
} else {
// 2.2.7.3 resolve in same way as current promise
consumer[resolver](promise.value);
}
} catch (e) {
// 2.2.7.2
consumer.reject(e);
};
})
});
};
// The Promise Resolution Procedure: will treat values that are thenables/promises
// and will eventually call either fulfill or reject/throw.
MyPromise.prototype.resolve = function(x) {
var wasCalled, then;
// 2.3.1
if (this === x) {
throw new TypeError('Circular reference: promise value is promise itself');
}
// 2.3.2
if (x instanceof MyPromise) {
// 2.3.2.1, 2.3.2.2, 2.3.2.3
x.then(this.resolve.bind(this), this.reject.bind(this));
} else if (x === Object(x)) { // 2.3.3
try {
// 2.3.3.1
then = x.then;
if (typeof then === 'function') {
// 2.3.3.3
then.call(x, function resolve(y) {
// 2.3.3.3.3 don't allow multiple calls
if (wasCalled) return;
wasCalled = true;
// 2.3.3.3.1 recurse
this.resolve(y);
}.bind(this), function reject(reasonY) {
// 2.3.3.3.3 don't allow multiple calls
if (wasCalled) return;
wasCalled = true;
// 2.3.3.3.2
this.reject(reasonY);
}.bind(this));
} else {
// 2.3.3.4
this.fulfill(x);
}
} catch(e) {
// 2.3.3.3.4.1 ignore if call was made
if (wasCalled) return;
// 2.3.3.2 or 2.3.3.3.4.2
this.reject(e);
}
} else {
// 2.3.4
this.fulfill(x);
}
}
上面构建的构造函数创建的东西更像是 Deferred 对象,即公开then
和resolve
方法。更糟糕的是,reject
和status
属性是可写的。因此,将此视为不安全的Deferred对象的构造函数更合乎逻辑,并创建一个单独的Promise构造函数,该构造函数构建于此,但只公开所需的内容:value
方法和构造函数回调可以访问then
和resolve
。
延迟对象可以不使用构造函数回调参数,并通过reject
属性提供对纯promise对象的访问:
promise
此代码可以进行多种优化,例如将Deferred方法设置为私有函数,并将类似的代码合并到更短的代码块中,但现在它可以非常清楚地显示每个需求的覆盖范围。
快乐的编码。
答案 2 :(得分:0)
var MyPromise = function(callback) {
this.callbacks = [];
callback(this.resolve.bind(this));
}
MyPromise.prototype.resolve = function(data) {
var callback = this.callbacks.pop();
var result = callback(data);
if (!result) return;
if (result instanceof MyPromise) {
var resolve = this.resolve.bind(this);
return result.then(function(d) {
return resolve(d);
});
}
return this.resolve(result);
}
MyPromise.prototype.then = function(callback) {
this.callbacks.unshift(callback);
return this;
}
答案 3 :(得分:0)
我的解决方案
function Promise(resolver){
if(typeof resolver !== 'function') {
throw new TypeError(`Promise resolver ${resolver} is not a function`)
}
this.state = 'pending'
this.value = void 0
try{
resolver(this.resolve.bind(this), this.reject.bind(this))
}catch(error){
this.reject.call(this,error)
}
}
Promise.prototype.resolve = function(value) {
if(this.state !== 'pending') return
this.value = value
this.state = 'fulfilled'
setTimeout( () => {
if(!this.onFulfilled) return
this.onFulfilled(value)
}, 0)
};
Promise.prototype.reject = function(reason){
if(this.state !== 'pending') return
this.value = reason
this.state = 'rejected'
setTimeout( () => {
if(this.onRejected){
this.onRejected(reason)
}else{
throw `Uncaught (in promise) ${reason}`
}
}, 0)
};
Promise.prototype.then = function(fulfilled, rejected){
if ( typeof fulfilled !== 'function' && typeof rejected !== 'function' ) {
return this;
}
if (typeof fulfilled !== 'function' && this.state === 'fulfilled' ||
typeof rejected !== 'function' && this.state === 'rejected') {
return this;
}
var self = this
return new Promise( (resolve, reject) => {
if(fulfilled && typeof fulfilled == "function"){
var onFulfilled = function (){
try{
var result = fulfilled(self.value)
if(result && typeof result.then === 'function'){
result.then(resolve, reject)
}else{
resolve(result)
}
}catch(error){
reject(error)
}
}
if(self.state === 'pending'){
self.onFulfilled = onFulfilled
}else if(self.state === 'fulfilled'){
onFulfilled()
}
}
if(rejected && typeof rejected == "function"){
var onRejected = function (){
try{
var result = rejected(self.value)
if(result && typeof result.then === 'function'){
result.then(resolve, reject)
}else{
resolve(result)
}
}catch(error){
reject(error)
}
}
if( self.state === 'pending'){
self.onRejected = onRejected
}else if(self.state === 'rejected'){
onRejected()
}
}
})
}
/*
* the methods don't in Promise/A+
*/
Promise.prototype.catch = function(onRejected){
return this.then(null, onRejected)
}
Promise.all = function(iterable){
if(typeof iterable[Symbol.iterator] !== 'function'){
throw new TypeError(`${iterable[Symbol.iterator]} is not a function`)
}
// Array,TypedArray,String,arguments ==> length; Map,Set ==> size
let len = [...iterable].length, i = 0, counter = 0, res = [];
return new Promise( (resolve, reject) => {
for(let item of iterable){
( (i) => {
Promise.resolve(item).then(function(value){
counter++
res[i] = value
if(counter == len){
resolve(res)
}
},function(reason){
if(!called){
reject(reason)
}
})
})(i++)
}
})
}
Promise.race = function(iterable){
if(typeof iterable[Symbol.iterator] !== 'function'){
throw new TypeError(`${iterable[Symbol.iterator]} is not a function`)
}
return new Promise( (resolve,reject) => {
for(let item of iterable){
Promise.resolve(item).then(function(value){
resolve(value)
},function(reason){
reject(reason)
})
}
})
}
Promise.resolve = function(value){
//if(value instanceof this) return value
//if(value instanceof Promise) return value
if(value.constructor !== Promise) return value
return new Promise( (resolve,reject) => {
if(value && typeof value === 'object' && typeof value.then === 'function'){
resolve( value.then( v => v))
}else{
resolve(value)
}
})
}
Promise.reject = function(reason){
return new Promise( (resolve,reject) => {
reject(reason)
})
}
答案 4 :(得分:0)
我试图用ES6来实现。发布,因为它可能对其他人有用
class MyPromise {
_value = null;
_isRejected = false;
_fullFilled = false;
_handlers = [];
_errorHandlers = [];
_error = null;
constructor(func) {
func(this._resolve, this._reject);
}
_resolve = (value) => {
this._value = value;
this._fullFilled = true;
this._handlers.forEach(handler => handler(value));
};
_reject = (error) => {
this._isRejected = true;
this._error = error;
this._errorHandlers.forEach(errorHandler => errorHandler(error));
};
catch(errorHandler){
return new MyPromise((resolve, reject) => {
this._errorHandler(resolve, reject, errorHandler)
})
}
_errorHandler(resolve, reject, callback){
const runErrorHandler = () => {
let error;
let returnedFromCatchCallback;
try{
returnedFromCatchCallback = callback(this._error);
}catch(_error){
error = _error;
reject(error);
}
resolve(returnedFromCatchCallback);
};
if(this._isRejected){
runErrorHandler(this._error);
}
this._errorHandlers.push(runErrorHandler);
}
then(handler, errorHandler) {
const returnOfHandler = new MyPromise((resolve, reject) => {
const runHandler = (value) => {
try{
resolve(handler(value));
}catch(error){
reject(error);
}
};
this._handlers.push(runHandler);
if(this._fullFilled) {
runHandler(this._value);
}
this._errorHandler(resolve, reject, errorHandler);
});
return returnOfHandler;
}
}
export default MyPromise;