我有一个纯JavaScript承诺(内置实现或poly-fill):
var promise = new promise(函数(解析,拒绝){/*…* /});
从规范来看,Promise可以是:
" settle "和" resolved "
“解决”和“拒绝”
“等待”
我有一个用例,我希望同步审问承诺并确定:
承诺达成了吗?
如果是,承诺解决了吗?
我知道我可以使用#then()来安排在Promise改变状态后异步执行的工作。我不是在问你该怎么做。
这个问题是关于Promise状态的同步询问。我怎样才能做到这一点呢?
如果你正在使用ES7实验版,你可以使用async轻松地包装你想要收听的承诺。
async function getClient() {
let client, resolved = false;
try {
client = await new Promise((resolve, reject) => {
let client = new Client();
let timer = setTimeout(() => {
reject(new Error(`timeout`, 1000));
client.close();
});
client.on('ready', () => {
if(!resolved) {
clearTimeout(timer);
resolve(client);
}
});
client.on('error', (error) => {
if(!resolved) {
clearTimeout(timer);
reject(error);
}
});
client.on('close', (hadError) => {
if(!resolved && !hadError) {
clearTimeout(timer);
reject(new Error("close"));
}
});
});
resolved = true;
} catch(error) {
resolved = true;
throw error;
}
return client;
}
您可以扩展Promise类来创建一个新的可查询的Promise
类。
您可以通过继承本机可用的Promise类来创建自己的子类,比如QueryablePromise,它的实例上有一个可用的status属性,您可以使用该属性同步查询Promise对象的状态。它的实现可以在下面看到,或者参考这个来获得更好的解释。
class QueryablePromise extends Promise {
constructor (executor) {
super((resolve, reject) => executor(
data => {
resolve(data)
this._status = 'Resolved'
},
err => {
reject(err)
this._status = 'Rejected'
},
))
this._status = 'Pending'
}
get status () {
return this._status
}
}
// Create a promise that resolves after 5 sec
var myQueryablePromise = new QueryablePromise((resolve, reject) => {
setTimeout(() => resolve(), 5000)
})
// Log the status of the above promise every 500ms
setInterval(() => {
console.log(myQueryablePromise.status)
}, 500)
你可以这样包装你的承诺
function wrapPromise(promise) {
var value, error,
settled = false,
resolved = false,
rejected = false,
p = promise.then(function(v) {
value = v;
settled = true;
resolved = true;
return v;
}, function(err) {
error = err;
settled = true;
rejected = true;
throw err;
});
p.isSettled = function() {
return settled;
};
p.isResolved = function() {
return resolved;
};
p.isRejected = function() {
return rejected;
};
p.value = function() {
return value;
};
p.error = function() {
return error;
};
var pThen = p.then, pCatch = p.catch;
p.then = function(res, rej) {
return wrapPromise(pThen(res, rej));
};
p.catch = function(rej) {
return wrapPromise(pCatch(rej));
};
return p;
}
2019:
据我所知,做到这一点的简单方法是thenable,超薄包装承诺或任何异步作业。
const sleep = (t) => new Promise(res => setTimeout(res,t));
const sleeping = sleep(30);
function track(promise){
let state = 'pending';
promise = promise.finally( _=> state ='fulfilled');
return {
get state(){return state},
then: promise.then.bind(promise), /*thentable*/
finally:promise.finally.bind(promise),
catch:promise.catch.bind(promise),
}
}
promise = track(sleeping);
console.log(promise.state) // pending
promise.then(function(){
console.log(promise.state); // fulfilled
})
这一基本功能的缺失确实令人恼火。如果你正在使用node.js,那么我知道有两种变通方法,它们都不太漂亮。下面的两个代码段实现了相同的API:
> Promise.getInfo( 42 ) // not a promise
{ status: 'fulfilled', value: 42 }
> Promise.getInfo( Promise.resolve(42) ) // fulfilled
{ status: 'fulfilled', value: 42 }
> Promise.getInfo( Promise.reject(42) ) // rejected
{ status: 'rejected', value: 42 }
> Promise.getInfo( p = new Promise(() => {}) ) // unresolved
{ status: 'pending' }
> Promise.getInfo( Promise.resolve(p) ) // resolved but pending
{ status: 'pending' }
用这两种方法似乎都无法区分最后两种承诺状态。
1. 使用V8调试API
这和util是一样的。检查使用。
const Debug = require('vm').runInDebugContext('Debug');
Promise.getInfo = function( arg ) {
let mirror = Debug.MakeMirror( arg, true );
if( ! mirror.isPromise() )
return { status: 'fulfilled', value: arg };
let status = mirror.status();
if( status === 'pending' )
return { status };
if( status === 'resolved' ) // fix terminology fuck-up
status = 'fulfilled';
let value = mirror.promiseValue().value();
return { status, value };
};
2. 同步运行微任务
这避免了调试API,但是会导致所有挂起的微任务和进程,从而产生一些可怕的语义。nextTick回调将同步运行。它还有一个副作用,就是防止被检查的承诺触发“未处理的承诺拒绝”错误。
Promise.getInfo = function( arg ) {
const pending = {};
let status, value;
Promise.race([ arg, pending ]).then(
x => { status = 'fulfilled'; value = x; },
x => { status = 'rejected'; value = x; }
);
process._tickCallback(); // run microtasks right now
if( value === pending )
return { status: 'pending' };
return { status, value };
};