Как я могу синхронно определить состояние обещания JavaScript?
У меня есть чистое обещание JavaScript (встроенная реализация или Поли-заполнение):
var promise = new Promise(function (resolve, reject) { /* ... */ });
С спецификация обещание может быть одним из следующих:
- "улажено" и "решено"
- "улажено" и "отвергнуто"
- 'в ожидании'
у меня есть прецедент, когда я хочу допросить обещание синхронно и определить:
- это обещание договорились?
если да, то обещание выполнено?
Я знаю, что я могу использовать #then()
чтобы запланировать работу, которая будет выполняться асинхронно после изменения состояния обещания. Я не спрашиваю, как это сделать.
этот вопрос конкретно о синхронный допрос состояния обещания. Как я могу этого достичь?
15 ответов:
такого синхронного API проверки не существует для собственных обещаний JavaScript. это невозможно сделать с родными обещаниями. В спецификации такой метод не указан.
библиотеки Userland могут это сделать, и если вы нацелены на определенный движок( например, v8) и имеете доступ к платформа код (то есть, вы можете писать код в базовый), то вы можете использовать специальные инструменты (например, закрытые символы) для достижения этой цели. Это очень конкретный хотя и не в пространстве пользователя.
вы можете сделать гонку с обещанием.решите
Это не синхронно, но происходит сейчасfunction promiseState(p, isPending, isResolved, isRejected) { Promise.race([p, Promise.resolve('a value that p should not return')]).then(function(value) { if (value == 'a value that p should not return') { (typeof(isPending) === 'function') && isPending(); }else { (typeof(isResolved) === 'function') && isResolved(value); } }, function(reason) { (typeof(isRejected) === 'function') && isRejected(reason); }); }
немного скриптов для тестирования и понимания их значения асинхронно
var startTime = Date.now() - 100000;//padding trick "100001".slice(1) => 00001 function log(msg) { console.log((""+(Date.now() - startTime)).slice(1) + ' ' + msg); return msg;//for chaining promises }; function prefix(pref) { return function (value) { log(pref + value); return value; };} function delay(ms) { return function (value) { var startTime = Date.now(); while(Date.now() - startTime < ms) {} return value;//for chaining promises }; } setTimeout(log, 0,'timeOut 0 ms'); setTimeout(log, 100,'timeOut 100 ms'); setTimeout(log, 200,'timeOut 200 ms'); var p1 = Promise.resolve('One'); var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "Two"); }); var p3 = Promise.reject("Three"); p3.catch(delay(200)).then(delay(100)).then(prefix('delayed L3 : ')); promiseState(p1, prefix('p1 Is Pending '), prefix('p1 Is Resolved '), prefix('p1 Is Rejected ')); promiseState(p2, prefix('p2 Is Pending '), prefix('p2 Is Resolved '), prefix('p2 Is Rejected ')); promiseState(p3, prefix('p3 Is Pending '), prefix('p3 Is Resolved '), prefix('p3 Is Rejected ')); p1.then(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : ')); p2.then(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : ')); p3.catch(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : ')); log('end of promises'); delay(100)(); log('end of script');
результаты с задержкой(0) (комментарий в то время как в задержке)
00001 end of promises 00001 end of script 00001 Level 1 : One 00001 Level 1 : Three 00001 p1 Is Resolved One 00001 p2 Is Pending undefined 00001 p3 Is Rejected Three 00001 Level 2 : One 00001 Level 2 : Three 00001 delayed L3 : Three 00002 Level 3 : One 00002 Level 3 : Three 00006 timeOut 0 ms 00100 timeOut 100 ms 00100 Level 1 : Two 00100 Level 2 : Two 00101 Level 3 : Two 00189 timeOut 200 ms
и результаты этого теста с firefox (chrome держать порядок)
00000 end of promises 00100 end of script 00300 Level 1 : One 00300 Level 1 : Three 00400 p1 Is Resolved One 00400 p2 Is Pending undefined 00400 p3 Is Rejected Three 00400 Level 2 : One 00400 Level 2 : Three 00400 delayed L3 : Three 00400 Level 3 : One 00400 Level 3 : Three 00406 timeOut 0 ms 00406 timeOut 100 ms 00406 timeOut 200 ms 00406 Level 1 : Two 00407 Level 2 : Two 00407 Level 3 : Two
promiseState сделать .раса и. тогда : Уровень 2
нет, нет синхронизации API, но вот моя версия асинхронного
promiseState
(С помощью @Matthijs):function promiseState(p) { const t = {}; return Promise.race([p, t]) .then(v => (v === t)? "pending" : "fulfilled", () => "rejected"); } var a = Promise.resolve(); var b = Promise.reject(); var c = new Promise(() => {}); promiseState(a).then(state => console.log(state)); // fulfilled promiseState(b).then(state => console.log(state)); // rejected promiseState(c).then(state => console.log(state)); // pending
вы можете использовать (уродливый) Хак в узле.js, пока не будет предложен собственный метод:
util = require('util'); var promise1 = new Promise (function (resolve) { } var promise2 = new Promise (function (resolve) { resolve ('foo'); } state1 = util.inspect (promise1); state2 = util.inspect (promise2); if (state1 === 'Promise { <pending> }') { console.log('pending'); // pending } if (state2 === "Promise { 'foo' }") { console.log ('foo') // foo }
вы можете обернуть свои обещания таким образом
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; }
Это действительно очень раздражает, что эта основная функциональность отсутствует. Если вы используете узел.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. Используйте API отладки V8
Это тот же трюк, что
util.inspect
использует.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 отладки, но имеет некоторую пугающую семантику, вызывая все ожидающие микрозадачи и
process.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 }; };
Синяя птица.js предлагает следующее:http://bluebirdjs.com/docs/api/isfulfilled.html
var Promise = require("bluebird"); let p = Promise.resolve(); console.log(p.isFulfilled());
что вы можете сделать, это использовать переменную, чтобы сохранить, вручную установить состояние этой переменной, и проверять эту переменную.
var state = 'pending'; new Promise(function(ff, rjc) { //do something async if () {//if success state = 'resolved'; ff();// } else { state = 'rejected'; rjc(); } }); console.log(state);//check the state somewhere else in the code
конечно, это означает, что вы должны иметь доступ к исходному коду обещание. Если вы этого не сделаете, то вы можете сделать:
var state = 'pending'; //you can't access somePromise's code somePromise.then(function(){ state = 'resolved'; }, function() { state = 'rejected'; }) console.log(state);//check the promise's state somewhere else in the code
мое решение-это больше кодирования, но я думаю, что вам, вероятно, не придется делать это для каждого обещания, которое вы используете.
по состоянию на узел.js версии 8, Теперь вы можете использовать мудрый-инспекции пакет для синхронной проверки родных обещаний (без каких-либо опасных хаков).
вы можете добавить метод, чтобы обещать.прототип. Выглядит это так:
редактировать: первое решение не работает, как и большинство ответов здесь. Он возвращает "ожидание" до асинхронной функции".затем " вызывается, что происходит не сразу. (То же самое касается решений с использованием Promise.гонка.) Мое второе решение решает эту проблему.
if (window.Promise) { Promise.prototype.getState = function () { if (!this.state) { this.state = "pending"; var that = this; this.then( function (v) { that.state = "resolved"; return v; }, function (e) { that.state = "rejected"; return e; }); } return this.state; }; }
вы можете использовать его на любые обещания. Например:
myPromise = new Promise(myFunction); console.log(myPromise.getState()); // pending|resolved|rejected
второй (и правильное) решение:
if (window.Promise) { Promise.stateable = function (func) { var state = "pending"; var pending = true; var newPromise = new Promise(wrapper); newPromise.state = state; return newPromise; function wrapper(resolve, reject) { func(res, rej); function res(e) { resolve(e); if (pending) { if (newPromise) newPromise.state = "resolved"; else state = "resolved"; pending = false; } } function rej(e) { reject(e); if (pending) { if (newPromise) newPromise.state = "rejected"; else state = "rejected"; pending = false; } } } }; }
и использовать его:
обратите внимание: в этом решении вам не нужно использовать оператор "new".
myPromise = Promise.stateable(myFunction); console.log(myPromise.state); // pending|resolved|rejected
Если вы используете ES7 experimental, вы можете использовать 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; }
Я написал небольшой пакет npm, promise-value, который предоставляет обертку promise с
resolved
флаг:https://www.npmjs.com/package/promise-value
Он также дает синхронный доступ к значению обещания (или ошибке). Это не изменяет сам объект Promise, следуя шаблону wrap, а не extend.
Это старый вопрос, но я пытался сделать что-то подобное. Мне нужно, чтобы держать работников собирается. Они структурированы в обещание. Мне нужно сканировать и посмотреть, разрешены ли они, отклонены или все еще ожидают. Если решено, мне нужно значение, если отклонено сделать что-то, чтобы исправить проблему или в ожидании. Если решено или отклонено, мне нужно начать другую задачу, чтобы продолжить работу. Я не могу выяснить способ сделать это.все или обещаю.гонка, как я продолжаю работать обещания в массиве и не может найти нет способ их удаления. Поэтому я создаю работника, который делает трюк
мне нужна функция генератора обещаний, которая возвращает обещание, которое разрешает или отклоняет по мере необходимости. Он вызывается функцией, которая устанавливает структуру, чтобы знать, что делает обещание.
в коде ниже генератор просто возвращает обещание на основе setTimeout.
здесь
//argObj should be of form // {succeed: <true or false, nTimer: <desired time out>} function promiseGenerator(argsObj) { let succeed = argsObj.succeed; let nTimer = argsObj.nTimer; return new Promise((resolve, reject) => { setTimeout(() => { if (succeed) { resolve('ok'); } else { reject(`fail`); } }, nTimer); }) } function doWork(generatorargs) { let sp = { state: `pending`, value: ``, promise: "" }; let p1 = promiseGenerator(generatorargs) .then((value) => { sp.state = "resolved"; sp.value = value; }) .catch((err) => { sp.state = "rejected"; sp.value = err; }) sp.promise = p1; return sp; }
doWork возвращает объект, содержащий обещание и его состояние и возвращается значение.
следующий код запускает цикл, который проверяет состояние и создает новых работников, чтобы сохранить его на 3 работающих рабочих.
let promiseArray = []; promiseArray.push(doWork({ succeed: true, nTimer: 1000 })); promiseArray.push(doWork({ succeed: true, nTimer: 500 })); promiseArray.push(doWork({ succeed: false, nTimer: 3000 })); function loopTimerPromise(delay) { return new Promise((resolve, reject) => { setTimeout(() => { resolve('ok'); }, delay) }) } async function looper() { let nPromises = 3; //just for breaking loop let nloop = 0; //just for breaking loop let i; //let continueLoop = true; while (true) { await loopTimerPromise(900); //execute loop every 900ms nloop++; //console.log(`promiseArray.length = ${promiseArray.length}`); for (i = promiseArray.length; i--; i > -1) { console.log(`index ${i} state: ${promiseArray[i].state}`); switch (promiseArray[i].state) { case "pending": break; case "resolved": nPromises++; promiseArray.splice(i, 1); promiseArray.push(doWork({ succeed: true, nTimer: 1000 })); break; case "rejected": //take recovery action nPromises++; promiseArray.splice(i, 1); promiseArray.push(doWork({ succeed: false, nTimer: 500 })); break; default: console.log(`error bad state in i=${i} state:${promiseArray[i].state} `) break; } } console.log(``); if (nloop > 10 || nPromises > 10) { //should do a Promise.all on remaining promises to clean them up but not for test break; } } } looper();
проверено в узле.js
Я нашел это решение простым и позволяю мне продолжать использовать собственные обещания, но добавлять полезные синхронные проверки. Мне также не нужно было тянуть всю библиотеку обещаний.
предостережение: это работает только в том случае, если есть какой-то перерыв в текущем потоке выполнения, чтобы разрешить выполнение обещаний перед проверкой синхронных конструкций. Это делает это более ограниченной полезностью, чем я изначально думал - все еще полезно для моего случая использования (спасибо Бенджамин Gruenbaum для указания на это)
/** * This function allow you to modify a JS Promise by adding some status properties. * Based on: http://stackoverflow.com/questions/21485545/is-there-a-way-to-tell-if-an-es6-promise-is-fulfilled-rejected-resolved * But modified according to the specs of promises : https://promisesaplus.com/ */ function MakeQuerablePromise(promise) { // Don't modify any promise that has been already modified. if (promise.isFulfilled) return promise; // Set initial state var isPending = true; var isRejected = false; var isFulfilled = false; // Observe the promise, saving the fulfillment in a closure scope. var result = promise.then( function(v) { isFulfilled = true; isPending = false; return v; }, function(e) { isRejected = true; isPending = false; throw e; } ); result.isFulfilled = function() { return isFulfilled; }; result.isPending = function() { return isPending; }; result.isRejected = function() { return isRejected; }; return result; } wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);
From https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved который основывал свой ответ на есть ли способ узнать, выполнено ли обещание ES6 / отклонено / разрешено?