如果您对取消vanillaECMAScript6Promise链感兴趣,那么本文将是一篇不错的选择,我们将为您详在本文中,您将会了解到关于取消vanillaECMAScript6Promise链的详细
如果您对取消vanilla ECMAScript 6 Promise链感兴趣,那么本文将是一篇不错的选择,我们将为您详在本文中,您将会了解到关于取消vanilla ECMAScript 6 Promise链的详细内容,我们还将为您解答取消链接的英文的相关问题,并且为您提供关于Callback and Promise in Javascript、Callback Hell 和 ECMAScript6 Promise、ECMAScript 6 Promises(上):基本概念、ECMAScript 6 Promises(下):谈谈 API(一)的有价值信息。
本文目录一览:- 取消vanilla ECMAScript 6 Promise链(取消链接的英文)
- Callback and Promise in Javascript
- Callback Hell 和 ECMAScript6 Promise
- ECMAScript 6 Promises(上):基本概念
- ECMAScript 6 Promises(下):谈谈 API(一)
取消vanilla ECMAScript 6 Promise链(取消链接的英文)
是否有清除.then
JavaScript Promise
实例的方法?
我已经在QUnit之上编写了一个JavaScript测试框架。该框架通过在.NET中运行每个测试来同步运行测试Promise
。(很抱歉,此代码块的长度。我已尽我所能对其进行了评论,因此它不会那么乏味。)
/* Promise extension -- used for easily making an async step with a timeout without the Promise knowing anything about the function it''s waiting on */$$.extend(Promise, { asyncTimeout: function (timeToLive, errorMessage) { var error = new Error(errorMessage || "Operation timed out."); var res, // resolve() rej, // reject() t, // timeout instance rst, // reset timeout function p, // the promise instance at; // the returned asyncTimeout instance function createTimeout(reject, tempTtl) { return setTimeout(function () { // triggers a timeout event on the asyncTimeout object so that, // if we want, we can do stuff outside of a .catch() block // (may not be needed?) $$(at).trigger("timeout"); reject(error); }, tempTtl || timeToLive); } p = new Promise(function (resolve, reject) { if (timeToLive != -1) { t = createTimeout(reject); // reset function -- allows a one-time timeout different // from the one original specified rst = function (tempTtl) { clearTimeout(t); t = createTimeout(reject, tempTtl); } } else { // timeToLive = -1 -- allow this promise to run indefinitely // used while debugging t = 0; rst = function () { return; }; } res = function () { clearTimeout(t); resolve(); }; rej = reject; }); return at = { promise: p, resolve: res, reject: rej, reset: rst, timeout: t }; }});/* framework module members... */test: function (name, fn, options) { var mod = this; // local reference to framework module since promises // run code under the window object var defaultOptions = { // default max running time is 5 seconds timeout: 5000 } options = $$.extend({}, defaultOptions, options); // remove timeout when debugging is enabled options.timeout = mod.debugging ? -1 : options.timeout; // call to QUnit.test() test(name, function (assert) { // tell QUnit this is an async test so it doesn''t run other tests // until done() is called var done = assert.async(); return new Promise(function (resolve, reject) { console.log("Beginning: " + name); var at = Promise.asyncTimeout(options.timeout, "Test timed out."); $$(at).one("timeout", function () { // assert.fail() is just an extension I made that literally calls // assert.ok(false, msg); assert.fail("Test timed out"); }); // run test function var result = fn.call(mod, assert, at.reset); // if the test returns a Promise, resolve it before resolving the test promise if (result && result.constructor === Promise) { // catch unhandled errors thrown by the test so future tests will run result.catch(function (error) { var msg = "Unhandled error occurred." if (error) { msg = error.message + "\n" + error.stack; } assert.fail(msg); }).then(function () { // resolve the timeout Promise at.resolve(); resolve(); }); } else { // if test does not return a Promise, simply clear the timeout // and resolve our test Promise at.resolve(); resolve(); } }).then(function () { // tell QUnit that the test is over so that it can clean up and start the next test done(); console.log("Ending: " + name); }); });}
如果测试超时,我的超时Promise将会assert.fail()
在测试中进行测试,以便将测试标记为失败,这一切都很好,但是测试继续运行,因为测试Promise(result
)仍在等待解决它。
我需要取消考试的好方法。我可以通过在框架模块之类的地方创建一个字段this.cancelTest
,然后在测试中每隔一段时间(例如在每次then()
迭代的开始)检查是否取消来做到这一点。但是,理想情况下,我可以$$(at).on("timeout",/* something here */)
用来清除变量中的剩余then()
s result
,以便不运行其余所有测试。
是否存在这样的东西?
快速更新
我尝试使用Promise.race([result, at.promise])
。没用
更新2 +混乱
为了解除对我的限制,我mod.cancelTest
在测试思路中在/ polling上添加了几行。(我还删除了事件触发器。)
return new Promise(function (resolve, reject) { console.log("Beginning: " + name); var at = Promise.asyncTimeout(options.timeout, "Test timed out."); at.promise.catch(function () { // end the test if it times out mod.cancelTest = true; assert.fail("Test timed out"); resolve(); }); // ...}).then(function () { // tell QUnit that the test is over so that it can clean up and start the next test done(); console.log("Ending: " + name);});
我在catch
语句中设置了一个断点,并且该命中率很高。现在让我感到困惑的是,该then()
声明没有被调用。有想法吗?
更新3
想通了最后一件事。 fn.call()
抛出了一个我没有捕获的错误,因此测试承诺在at.promise.catch()
解决之前就被拒绝了。
答案1
小编典典是否有清除.then
JavaScript Promise实例的方法?
不。至少在ECMAScript 6中没有。then
默认情况下,不幸的是,承诺(及其处理程序)是无法取消的。关于es-
discuss有一些有关如何正确执行此操作的讨论,但是无论哪种方法获胜,都不会在ES6中实现。
当前的观点是,子类化将允许使用您自己的实现来创建可取消的Promise(不确定其效果如何) 。
在语言委托人找到最佳方法之前 (希望是ES7?), 您仍然可以使用userland Promise实现,其中许多功能都取消了。
Callback and Promise in Javascript
Javascript 传统的调用方式有 Callback。在调用链中现在经常使用 promise, derfer 等
Callback 的一些缺点
- Callback 调用层次多了,会递进多层。不利于代码编写,也不利于代码重用
- 没有一个统一的错误处理,需要在每个方法里面处理,并控制程序流程。
为了解决这些缺点,采用了调用链的方式, promise 或者 derfer
- Promise 解决了递进调用的问题。有利于代码的编写,也利于代码的重用。
- 调用链上增加了统一错误处理的模型,可以 catch error,然后统一处理。
Promise 简单使用
promiseMeSomething()
.then(function (value) {
}, function (reason) {
});
var outputPromise = getInputPromise()
.then(null, function (error) {
});
function authenticate() {
return getUsername()
.then(function (username) {
return getUser(username);
})
// chained because we will not need the user name in the next event
.then(function (user) {
return getPassword()
// nested because we need both user and password next
.then(function (password) {
if (user.passwordHash !== hash(password)) {
throw new Error("Can''t authenticate");
}
});
});
}
Promise 简单实现
var Promise = function () {
this.okCallbacks = [];
this.koCallbacks = [];
};
Promise.prototype = {
okCallbacks: null,
koCallbacks: null,
then: function (okCallback, koCallback) {
okCallbacks.push(okCallback);
if (koCallback) {
koCallbacks.push(koCallback);
}
}
};
Callback Hell 和 ECMAScript6 Promise
Promise
是 “14nodejs(7 天)” 第五天的课程,但是 Promise 并不属于 Node.js 的内容,而是 ECMScript6 新增的 API
回调地域(Callback Hell)
回调地域
既一个异步请求需要另一个异步请求结果
$.ajax({url, success: function () { ... }})
$.ajax({url, success: function () { ... }})
$.ajax({url, success: function () { ... }})
$.ajax({url, success: function () { ... }})
由于 Javascript 是单线程的,所以这里执行顺序是 ajax1 -> ajax2 -> ajax3 -> ajax4
但是又由于这四个是异步操作,所以这种多线程操作会导致执行顺序不固定
为了保障异步函数的执行顺序,可以通过异步套异步的方式解决
$.ajax({url, success: function () {
$.ajax({url, success: function () {
$.ajax({url, success: function () {
$.ajax({url, success: function () { ... }})
}})
}})
}})
以上这种写法就是所谓的回调地域,非常的不利于维护
为了解决这一情况,ECMAScript6
新增了 Promise API
Promise(承诺)
可以理解 Promise
为一个容器,它包含三个状态,分别为 Pending(正在执行)
、Resolved(已解决)
、Rejected(未解决)
Promise
本身并不是异步的,但其包裹的内容函数为异步函数
// 通过 new Promise 创建 Promise 容器
// 实例化 Promise
var p1 = new Promise (function (resolve, reject) { // Promise 容器(它本身不是异步方法)
$.ajax({ // $.ajax 异步方法
url,
success: function () {
resolve(data) // 改变容器状态为成功
},
error: function () {
reject(err) // 改变容器状态为失败
}
})
})
容器接收两个形参:
resolve
改变容器状态为成功reject
改变容器状态为失败
通过 .then()
决定实例成功后的指定操作
p1.then(function (data) {
console.log(data) // 接收 p1 实例成功状态 resolve 抛出的数据 data
return p2 // 返回第二个 Promise 容器
}, function (err) {
console.log(err) // 接收 p1 实例失败状态 reject 抛出的数据 data
})
.then
包含两个函数参数:
- 第一个函数的参数接收 Promise 容器中的
resolve
抛出的成功内容 - 第二个函数的参数接收 Promise 容器中的
reject
抛出的失败信息
在 .then
的第一个函数中可以返回数据,此数据可以提供给后续 .then
接收使用
- 如果没有 return 返回值,则后续接收到
undefined
- 如果 return 返回一个 Promise 对象时,后续
.then
第一个函数接收该 Promise 对象的resolve
状态,第二个函数接收该 Promise 对象的reject
状态
到此,通过以上的介绍可以 优化回调地域(Callback Hell)
// 通过 new Promise 创建 Promise 容器
// 实例化 Promise
var p1 = new Promise (function (resolve, reject) { // Promise 容器(它本身不是异步方法)
$.ajax({ // $.ajax 异步方法
url,
success: function () {
resolve(data) // 改变容器状态为成功
},
error: function () {
reject(err) // 改变容器状态为失败
}
})
})
var p2 = new Promise (function (resolve, reject) { ... })
var p3 = new Promise (function (resolve, reject) { ... })
var p4 = new Promise (function (resolve, reject) { ... })
// 操作实例
p1
.then(function (data) {
console.log(data) // 打印 p1 的 data 内容
return p2 // 向下传递实例 p2
}, function (err) {
console.log(err) // 打印 p1 的 err 信息
})
.then(function (data) {
console.log(data) // 打印 p2 的 data 内容
return p3 // 向下传递实例 p3
}, function (err) {
console.log(err) // 打印 p2 的 err 信息
})
.then(function (data) {
console.log(data) // 打印 p3 的 data 内容
return p4 // 向下传递实例 p4
}, function (err) {
console.log(err) // 打印 p3 的 err 信息
})
.then(function (data) {
console.log(data) // 打印 p4 的 data 内容
}, function (err) {
console.log(err) // 打印 p4 的 err 信息
})
通过判断是否有 then 方法判断其是否是 Promise 对象
var p1 = new Promise (function (resolve, reject) { ... }
function p2 () { ... }
// 替换下段代码中的 fn 即可查看 P1、P2 是否是 Promise 对象
if (!!fn && typeof fn.then === ''function'') {
console.log(''是Promise'')
} else {
console.log(''不是Promise'')
}
文章已同步我的个人博客:《Callback Hell 和 ECMAScript6 Promise》
资料参考:
- [Promise - JavaScript | MDN]
本文由博客一文多发平台 OpenWrite 发布!
ECMAScript 6 Promises(上):基本概念
原文地址: http://www.2ality.com/2014/09/es6-promises-foundations.html
原文作者:Dr. Axel Rauschmayer
译者:倪颖峰
尽管原博客已经标明:本博客文档已经过时,可进一步阅读“Exploring ES6”中的 “Asynchronous programming (background)”。但仔细对比了下,两者行文没有什么太大区别。另外相对原来的译文,修订了其中的一些错误,增补了event loop图。
本文主要介绍一下JS中异步编程的基础。这是一个主体两篇博文的第一部分,第二部分会涵盖在 ES6 中 promises 的API。
1. JavaScript 中的调用堆栈
当函数 f 调用一个函数 g,g 就需要知道它完成后在哪里返回(在 f 内部)。这里的主要方式是使用栈进行管理,即调用堆栈。来看一个实例。
function h( z ){
// Print stack trace
console.log( new Error.stack ); // (A)
}
function g( y ){
h( y+1 ); // (B)
}
function f( x ){
g( x+1 ); // (C)
}
f( 3 ); // (D)
return;
开始,当上面的程序运行时,调用堆栈为空。
当在 D 处函数调用 f( 3 )时,栈中有了一个项:
当前的全局作用域
当在 C 处调用函数 g( x+1 )时,栈中就有了两个项:
当前的 f
当前的全局作用域
在当 B 处调用函数 h( y+1 )时,栈中含有了三项:
当前的 g
当前的 f
当前的全局作用域
在 A 处的栈跟踪内会展示出下面的调用堆栈:
Error
at h (stack_trace.js:2:17)
at g (stack_trace.js:6:5)
at f (stack_trace.js:9:5)
at <global> (stack_trace.js:11:1)
接下来,每当一个函数执行结束,栈中就会相应的删除顶部的对应项。当函数 f 结束后,就会回到全局作用域,然后调用栈变为空。在 E 处,返回是栈为空,意味着程序的结束。
2. 浏览器的事件循环
简而言之,每一个浏览器页面tab都有独立的进程,事件循环。该循环会执行浏览器相关的由一个任务队列分发的事务(称为 任务)。任务实例有:
1. 解析 HTML
2. 执行 script 标签中的 JS 代码
3. 处理用户交互(点击鼠标,键盘输入等)
4. 处理网络异步请求结果
2 - 4任务均是通过浏览器内置引擎来执行JS代码。在代码结束时结束。然后队列中的下一项任务开始被执行。下面图片展示了所有的机制是如何连接起来的。
事件循环是掺杂在其他并行运行的各种进程中(定时器,输入处理器等)。这些进程同通过向其队列添加任务进行通信。
2.1 定时器
浏览器具有定时器,setTimeout() 来创建定时器,等到触发时刻就会向其队列添加一个任务。如下标识:
setTimeout( callback, ms );
在ms毫秒之后, callback 回调函数就被添加到任务队列中。需要注意的是 ms 只是指定回调函数添加的事件,并不保证被执行。特别是如果事件循环被阻塞,那可能会在很久才执行(文章后面会提到)。
当 setTimeout() ms 设置为零是一个较为通用的方法来向队列立即添加任务。然而一些浏览器并不允许ms 设置低于最小限度(FF 中为4ms);如果小于最小限度,那也会将被设置成最小限度值。
2.2 显示DOM的变化
对于许多的DOM变化(特别是会引起重排re-layout的),也不会立即更新显示。“布局每16ms才会刷新一次”而且必须给予其通过事件循环执行的机会。
有一些办法来协调浏览器频繁的DOM更新,来避免布局结果的冲突。查询一下关于 requestAnimationFrame() 的详细文档吧。
2.3 运行至完成 的语义
JS 所谓的运行至完成语义:当前任务总是在下一个任务执行前完成。这意味着每一个任务都可以完全控制所有的当前状态,并且不需要担心并发的修改。
看个例子:
setTimeout( function(){
console,log(''Second''); // (A)
}, 0 );
console,log(''First''); // (B)
在A处开始的函数是被立即添加到队列中的,但是只有在当前代码块完成之后(具体就是 B 处),才能执行。意味着代码输出永远是:
First Second
2.4 阻塞事件循环
我们可以发现,每个tab(在一些浏览器,完整的浏览器)被单独的进程控制-包括用户界面和其它所有的运算。这意味着你在进程中执行长时间的运算将会使得用户界面被定格。演示代码如下:
<a id="block" href="">Block for 5 seconds</a>
<p>
<button>This is a button</button>
<div id="statusMessage"></div>
<script>
document.getElementById(''block'')
.addEventListener(''click'', onClick);
function onClick(event) {
event.preventDefault();
setStatusMessage(''Blocking...'');
// Call setTimeout(), so that browser has time to display
// status message
setTimeout(function () {
sleep(5000);
setStatusMessage(''Done'');
}, 0);
}
function setStatusMessage(msg) {
document.getElementById(''statusMessage'').textContent = msg;
}
function sleep(milliseconds) {
var start = Date.now();
while ((Date.now() - start) < milliseconds);
}
</script>
当函数执行时,同步函数 sleep() 阻塞事件循环5秒钟,在这些时间里用户界面将被锁定。例如不能点击“Simple button”。
2.5 避免阻塞
你可以使用两种方式来避免阻塞事件循环:
首先,不要在主进程中执行长时间的计算,将其移到其他进程中。可以通过 Worker API 来实现。
其次,不应该(以同步方式)等待一个长时间运算的结果( 在Worker进程中运行你的算法,或者以网络请求等 ),你可以继续执行事件循环,是运算在完成的时候通知你。事实上,在浏览器中你没有什么选择,而只能这么做。比如,并没有内置的 sleep 同步(例如之前使用的 sleep() 方法)。取而代之的是 setTimeout() 异步的sleep方法。
下面部分将会讲解一下关于异步等待结果的技术。
3. 异步接收结果
异步接收结果有两种常见方式:事件与回调函数。
3.1 通过事件异步接收结果
在这部分的异步接收结果内容中,你需要为每一个请求创建一个对象,对其注册事件绑定:一个为计算成功,一个为出错。下面代码展示对于 XMLHttpRequest API的工作原理:
let req = new XMLHttpRequest();
req.open(''GET'', url);
req.onload = function(){
if( req.status === 200 ){
processData( req.response );
}else{
console.log( ''ERROR'', req.statusText );
}
};
req.onerror = function(){
console.log(''Network Error'');
};
req.send();// add request to task queue
记住,事实上最后一行并不执行结果,它只是将其添加到任务队列。因此你也可以在 设置 onload和onerror之前,open() 之后正确的调用该方法。将会一样的执行,由于JS代码的 运行至完成的特性。
如果你使用多线程语言,indexedDB(索引型数据库)请求可能会引起相竞争的情况。因此,运行至完成的特性使得在JS变得安全可靠。
let openRequest = indexedDB.open( ''test'', 1 );
openRequest.onsuccess = function( event ){
console.log(''Success!'');
var db = event.target.result;
}
openRequest.onerror = function( error ){
cosole.log( error );
}
open() 不会立即打开数据库,它会在队列中添加一个任务,该任务会在当前所有任务执行完毕之后执行。这就是为何你可以(事实上也是必须可以)在调用 open() 之后注册事件句柄的原因。
3.2 通过回调函数异步接收结果
如果使用回调函数来异步接收结果,你需要将回调函数作为链接参数传递给异步函数或者方法进行调用。
下面为 Node.js 的实例。通过 fs.readFile() 异步读取文件内容:
// Node.js
fs.readFile( ''myfile.txt'', { encoding: ''utf8'' },
function(error, text){
if (error){ // (A)
// ...
}
console.log( text );
});
如果 readFile() 成功,在A处的回调函数就会通过参数 text 来传递结果值。如果失败,回调函数会通过第一个参数获取一个错误(一般来说是一个Error或者其子类)。
在经典的函数式编程风格中,代码可以是这样的:
// Functional
readFileFunctional( ''myfile.txt'', { encoding: ''utf8'' },
function( text ){
console.log( text ); // success
},
function( error ){ // failure
// .....
}
);
3.3 连续传递风格
使用回调函数的编程风格(特别是之前展示的函数式编程方式)又叫做连续传递风格(Continuation-Passing Style:CPS),因为下一步(the continuation)是当做一个参数被传递的。这使得调用函数可以得到更多的对于接下来运行的和什么时候运行的控制。
下面代码显示了所谓的CPS:
console.log(''A'');
identity(''B'', function step2(result2) {
console.log(result2);
identity(''C'', function step3(result3) {
console.log(result3);
});
console.log(''D'');
});
console.log(''E'');
// Output: A E B D C
function identity(input, callback) {
setTimeout(function () {
callback(input);
}, 0);
}
对于每一步,都在程序的控制流内进行回调。这就导致了有时被称为回调地狱的嵌套函数。当然,你完全可以避免嵌套,因为JS的函数声明是被提升的(它们被计算定义是在它们作用域的最开始阶段)。这意味着你可以提前调用和使用定义在程序后面的函数声明。下面代码就是使用提升使得之前的例子扁平化。
console.log(''A'');
identity(''B'', step2);
function step2(result2) {
// The program continues here
console.log(result2);
identity(''C'', step3);
console.log(''D'');
}
function step3(result3) {
console.log(result3);
}
console.log(''E'');
3.4 CPS中编写代码
在一般的JS风格中,我们使用以下方式来组合代码块:
1. 将其一个接一个放置。使代码变得一目了然,普通风格的级联式代码可以帮助我们提醒记住代码的正确组合顺序。
2. Array 的方法,比如 map(),filter() 和 forEach()。
3. 如 for 或者 while 的循环。
Async.js 这个库提供了让我们用 CPS 做的更简单的操作符,NodeJS 风格的回调函数。下面是一个被使用来加载存在数组中的三个文件内容。
let async = require(''async'');
let fileNames = [ ''foo.txt'', ''bar.txt'', ''baz.txt'' ];
async.map( fileNames,
function( fileName, callback ){
fs.readFile( fileName, { encoding: ''utf8'' }, callBack );
},
function( error, textArray ){
if( error ){
cosole.log( error );
return;
}
console.log( ''TEXTS:'' + textArray.join( " " ) );
});
3.5 回调函数的利弊
使用回调函数处理结果是一种完全不同的编程风格,CPS。CPS主要优点是很容易理解。当然,他有一些缺点:
1. 错误监控变的较为复杂:一般通过两种方式来处理报错,通过回调函数和异常。也需要小心两者相结合的情况。
2. 更少的信息:在同步函数中对于输入(参数)和输出(函数的返回值)会有一个明确的关系。在异步函数中使用回调函数,他们被混在一起:函数结果并不重要,并且一些参数作为输入,一些参数作为输出。
3. 组合变的更为复杂:因为有些输出就在参数中,这变成了更为复杂的组合器。
在 NodeJS 风格中回调函数有三个弊端(与函数式风格相比较):
1. 使用 if 语句处理错误状态增加了复杂程度。
2. 重用绑定函数较为困难。
3. 提供一个默认的错误处理函数也很困难。如果你对自己的函数调用不想写处理函数,那么默认的错误处理函数是非常有用的。它可以被使用在函数调用者没有指定特定的处理函数的时候。
4. 展望一下
第二部分主要包括了 promises 以及 ES6 中 promise API。Promises 底层的实现比回调函数复杂很多。当然,它也带来了挺多优势之处。
ECMAScript 6 Promises(下):谈谈 API(一)
原文地址: http://www.2ality.com/2014/10/es6-promises-api.html
原文作者:Dr. Axel Rauschmayer
译者:倪颖峰
原博客已经标明:本博客文档已经过时,可进一步阅读 “Exploring ES6” 中的 “Promises for asynchronous programming”。仔细对比了下,两者的确存在一些差异。本文是在原来的译文基础上修订的。
(文章第二部分实在是太长,所以在此分成两部分翻译)
本文是通过普通的 Promises 和 ES6 的 Promise API 来介绍异步编程。这是两篇系列文章的第二部分 - 第一部分介绍了一下异步编程的基础(你需要充分理解一下以便明白这篇文章)。
1. 概述
下面函数通过一个 Promise 异步返回结果:
function asyncFunc() {
return new Promise(
function (resolve, reject) {
resolve(value); // success
···
reject(error); // failure
});}
可以像下面这样来调用asyncFunc()
:
asyncFunc()
.then(value => { /* success */ })
.catch(error => { /* failure */ });
1.1 处理 Promises 数组
Promise.all()可以遍历一个
Promises 数组。
例如,可以通过数组方法 map ( ) 来创建一个 Promises 数组:
let fileUrls = [
''http://example.com/file1.txt'',
''http://example.com/file2.txt''
];
let promisedTexts = fileUrls.map(httpGet); // Array of Promises
如果对该数组应用Promise.all( )
,那么当所有的 Promises 被填充后将得到一个数组:
Promise.all(promisedTexts)
// Success
.then(texts => {
for (let text of texts) {
console.log(text);
}})
// Failure
.catch(reason => {
// Receives first rejection among `promisedTexts`
});
2. Promises
Promises 是一种解决特定异步编程的模式:函数(或者方法)异步返回其结果。为实现该功能,返回结果为具有占位符意义的一个对象 Promise。函数的调用者注册回调函数,一旦结果运算完毕就立即通知 Promise。函数会由 Promise 来传递结果。
JavaScript 的 Promises 事实标准称为 Promises/A+。ES6 的 Promise API 便遵循这个标准。
3. 第一个实例
看一下第一个实例,来了解下 Promises 是如何运行的。
NodeJS 风格的回调函数,异步读取文件如下所示:
fs.readFile(''config.json'',
function (error, text) {
if (error) {
console.error(''Error while reading config file'');
} else {
try {
let obj = JSON.parse(text);
console.log(JSON.stringify(obj, null, 4));
} catch (e) {
console.error(''Invalid JSON in file'');
}
}
});
使用 Promises,相同功能的实现可以是这样:
readFilePromisified(''config.json'')
.then(function (text) { // (A)
let obj = JSON.parse(text);
console.log(JSON.stringify(obj, null, 4));
})
.catch(function (reason) { // (B)
// File read error or JSON SyntaxError
console.error(''An error occurred'', reason);
});
这里依旧是有回调函数,但这里是通过方法来提供的,是在有结果时(then () 和 catch ())被调用的。在 B 处的报错的回调函数有两方面的优势:第一,这是一种单一风格的错误处理(与前一个例子中 if (error) 和 try-catch 代码对比下)。 第二,你可以一个代码地点同时处理 readFilePromisified () 的错误 和 A 处回调函数的错误。
readFilePromisified () 函数的实现代码见后面。
4. Promises 的创建和使用
从生成者和消耗者两方面来看一下 Promises 是如何操作的。
4.1. 生成一个 Promise
作为一个生成者,你创建一个 Promise 然后用它传递结果:
let promise = new Promise(
function(resolve, reject){ // (A)
...
if( ... ){
resolve( value );
} else {
reject( reason );
}
});
一个 Promise 一般处于以下三个(互斥)状态中的某一个状态:
Pending:还没有计算出结果
Fulfilled:成功计算出结果
Rejected:在计算过程中发生一个错误
一个 Promise 被设置后(settled:代表运算已经完成 ),它的状态要么是 fulfilled 要么是 rejected。每一个 Promise 只能设置一次,然后保持 settled 状态。之后再设置它将不起作用。
new Promise () 的参数( 在 A 处开始的 )称为 executor(执行器):
1. 如果运算成功,执行器会通过 resolve () 传递结果,这通常会 fufill Promise(后面将会解释,如果 resolve 的是一个 Promise 可能会不同,见后面解释)。
2. 如果错误发生了,执行器就会通过 reject () 通知 Promise 消费者,就会 reject Promise。
4.2. 使用 Promise
作为 Promise 的消费者,你会通过 reactions - 利用 then ( ) 方法注册的回调函数,得到 fufillment 或者 rejection 的通知 。
promise.then(
function( value ){/* fulfillment */},
function( reason ){/* rejection */}
);
正是由于一个 Promise 一旦被设置后(settled)再也不能变化,使得 Promises 对于异步函数来说非常有用(一次性使用结果)。此外,永远不会有任何竞争条件,因为不论 Promise 是在设置前还是在设置后调用 then ( ) 都是一样的:
1. 如果是在 Promise 设置前注册的 reactions, 那么一旦设置将得到通知。
2. 如果是在 Promise 设置后注册的 reactions, 那么会立即收到缓存的设置的值(像任务那样排队被激活)。
4.3. 只处理 fullfillment 或者 rejection
如果你只关心 fullfillment,你可以忽略 then () 的第二个参数:
promise.then(
function( value ){/* fulfillment */}
);
如果你只对 rejection 感兴趣,可以忽略第一个参数。也可以用更紧凑的 catch () 方法来实现。
promise.then(
null,
function( reason ){/* rejection */}
);
// 等价于
promise.catch(
function( reason ){/* rejection */}
);
这里推荐只用 then () 处理 fullfillment,使用 catch () 处理错误,因为这样可以更加优雅的标记回调函数,并且可以同时处理多个 Promises 的 rejection(稍后解释)。
5. 举例
在深入探究 Promises 之前,先使用前面学到的知识来看一些例子。
5.1 举例:fs.readFile () Promise 化
下面代码是将 Node.js 中的函数 fs.readFile()利用
Promise 来重写:
import {readFile} from ''fs'';
function readFilePromisified(filename) {
return new Promise(
function (resolve, reject) {
readFile(filename, { encoding: ''utf8'' },
(error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
readFilePromisified()的调用如下:
readFilePromisified(process.argv[2])
.then(text => {
console.log(text);
})
.catch(error => {
console.log(error);
});
5.2 举例:XMLHttpRequest promise 化
下面是一个基于 XMLHttpRequest API 事件,通过 Promise 编写的 HTTP GET 函数。
function httpGet( url ){
return new Promise(
function( resolve, reject ){
let request = new XMLHttpRequest();
request.onreadystatechange = function(){
if( this.status === 200 ){
// success
resolve( this.response );
}else {
reject( new Error( this.statusText ) );
}
}
request.onerror = function(){
reject( new Error(''XMLHttpRequest Error: '' + this.statusText ) );
};
request.open( ''GET'', url );
request.send();
});
}
下面是如何使用 httpGet ():
httpGet("http://example.com/file.txt")
.then(
function (value){
console.log(''contents: ''+ value);
},
function (reason){
console.log(''something error'', reason);
}
);
5.3 举例:延迟活动
使用 Promise 的 delay () 函数(类似于 Q.delay ())来实现 setTimeout( ) 。
function delay (ms){
return new Promise(function(resolve, reject){
setTimeout(resolve, ms); // (A)
});
}
// 使用 delay()
delay(5000).then(function(){ // (B)
console.log(''5s have passed'');
});
注意 A 处我们调用 resolve 没有传递参数,相当于调用了 resolve (undefined)。在 B 处我们不需要通过的返回值,就可以简单的忽略它。仅仅通知就已经 OK 了。
5.4 举例:Promise 超时
function timeout(ms, promise){
return new Promise(function(resolve, reject){
promise.then( resolve );
setTimeout(function(){
reject(new Error(''Timeout after '' + ms + '' ms'')); // (A)
}, ms)
});
}
注意在 A 处超时后 rejection 并不会取消这个请求,但是会阻止 Promise 达到 fulfilled 状态。
如下方式使用 timeout ():
timeout(5000, httpGet("http://example.com/file.txt") )
.then(function(value){
console.log(''contents: '' + value);
})
.catch(function(reason){
console.log(''error or timeout: '' , reason);
});
6. 链式调用 then ()
调用 P.then( onFulfilled, onRejected ) 会得到一个新的 Promise Q。这意味着在 Q 中,你可以通过调用 then () 来保持对 Promise 的控制流:
1. Q 在 onFulfilled 或者 onRejected 返回结果的时候,即为 resolved。
2. Q 在 onFulfilled 或者 onRejected 抛出异常的时候,即为 rejected。
6.1. Resolving Q 返回一般值
如果 then () 返回的是个一般值,那么可以 resolve 这个 Promise Q,你可以在下一个 then () 取到这个值:
asyncFunc()
.then(function(){
return 123;
})
.then(function(value){
console.log(value); // 123
})
6.2. Resolving Q 返回 then 对象(thenables)
如果 then () 返回的是个 then 对象 R,那么也可以 resolve 这个 Promise Q。then 对象(thenable)表示有 Promise 风格 then () 方法的任何对象,因此 Promises 就是 then 对象。resolve R (例如通过 onFulfilled 返回)意味着它被插入到 Q 之后:R 的 settlement 将被传递给 Q 的 onFulfilled 或者 onRejected 回调函数。也就是说 Q 转变成了 R。
这个形式主要是用来扁平化嵌套式调用 then (),比如下面的例子:
asyncFunc1()
.then(function(value1){
asyncFunc2()
.then(function(value2){
...
});
});
那么扁平化形式可以变为这样:
asyncFunc1()
.then(function(value1){
return asyncFunc2();
})
.then(function(value2){
...
});
6.3 Resolving Q from onRejected
如之前提到的,不管你在错误 handler 中返回什么,都将成为一个 fulfillment 的值(注意不是 rejection 值)。这使得你可以定义失败情况下用到默认值:
retrieveFileName()
.catch(function(){
// Something went wrong, use a default value
return ''Untitled.txt'';
})
.then(function(fileName){
...
});
6.4 抛出异常拒绝 Q(Rejecting Q by throwing exceptions)
从 then 的任意一个参数抛出的异常被传递给下一个错误 handler:
asyncFunc()
.then(funcrion(value){
throw new Error();
})
.catch(function(reason){
// Handle error here
});
6.5 执行过程中的异常(Exceptions in executors)
executor(new Promise () 的回调函数)中抛出的异常,将会传递到由 executor 管理的 Promise 的错误 handler 中:
new Promise(function(resolve, reject){
throw new Error();
})
.catch(function(err){
// Handle error here
});
6.6 链式的错误处理
会有一个或多个 then () 调用没有提供错误 handler,那么直到出现错误 handler 该错误才会被传递进去:
asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.catch(function(reason{
// something went wrong above
});
7. 组合
本章节会描述你如何组合现有的 Promises 来创建新的 Promise。我们已经使用过一种组合 Promise 的方式了:通过 then () 连续的链式调用。Promise.all () 和 Promise.race () 提供了另一些组合的形式。
7.1. 通过 Promise.all () 实现 map ()
庆幸的是,基于 Promise 的函数可以返回结果,因此很多同步工具仍然可以使用。比如你可以使用数组的方法 map ():
let fileUrls = [
''http://example.com/file1.txt'',
''http://example.com/file2.txt''
];
let promisedTexts = fileUrls.map(httpGet);
promisedTexts 是一个 Promises 数组。Promise.all () 可以处理一个 Promises 数组(then 对象和 其他值可以通过 Promise.resolve () 来转换为 Promises),一旦所有的项状态都为 fulfilled,就会得到取值的数组:
Promise.all(promisedTexts)
.then(texts=> {
for (let text of texts) {
console.log(text);
}
})
.catch(reason => {
// Receives first rejection among the Promises
});
7.2. 通过 Promise.race () 实现延时
Promise.race () 接受一个 Promises 数组(then 对象和其他值可以通过 Promise.resolve () 来转换为 Promises),返回一个 Promise 对象 P。第一个传入的 Promise 会将其 settlement 传递给输出的 Promise。
举个例子,使用 Promise.race () 来实现一个 timeout:
Promise.race([
httpGet(''http://example.com/file.txt''),
delay(5000).then(function(){
throw new Error(''Time out'');
})
])
.then(function(text){ ... })
.catch(function(reason){ ... });
8. Promises 总是异步的
一个 Promise 库,不管是同步(正常方式)还是异步(当前代码块之后继续执行直至完成)将结果送给 Promise 的 reactions,都可以完全控制。然而,Promises/A+ 规范约定总是用后一种方式。它以 then () 方法的需求(2.2.4)来描述:
只有当执行上下文栈中仅仅包含平台代码时,才可以调用 onFulfilled 或者 onRejected。
这意味着你可以依赖运行到完成的语态(run-to-completion semantics:第一部分中提到的),使得链式的 Promises 不会使其他任务没有时间得到处理。
关于取消vanilla ECMAScript 6 Promise链和取消链接的英文的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于Callback and Promise in Javascript、Callback Hell 和 ECMAScript6 Promise、ECMAScript 6 Promises(上):基本概念、ECMAScript 6 Promises(下):谈谈 API(一)等相关知识的信息别忘了在本站进行查找喔。
本文标签: