Another RayJune

Promise:针对异步的抽象

直到写了 Node.js,才知 Promise 的好。

前言

JavaScript 的独特之处,或者更准确的说,Node.js 的独特之处就在于采用了 异步非阻塞 的 I/O 模型。

多重异步必然带来回调地狱,我们来看看 Promise 是什么,带来了什么吧。

简介

通俗地说,Promise 就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理

专业地说,Promise抽象异步处理对象 以及对其进行各种操作的组件

举一个异步处理 + 回调函数的例子:

1
2
3
4
5
6
getAsync("fileA.txt", function(error, result){
if(error){// 取得失败时的处理
throw error;
}
// 取得成功时的处理
});

Promise 则是 把类似的异步处理对象和处理规则进行规范化, 并按照采用统一的接口来编写:

1
2
3
4
5
getAsyncPromise("fileA.txt").then(function(result){
// 获取文件内容成功时的处理
}).catch(function(error){
// 获取文件内容失败时的处理
});

下面我们来看基于 Promise 的两个实践。

实践一:初尝 Promise

1
2
3
4
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用 resolve 或 reject
});
1
2
3
4
5
6
7
8
9
10
11
12
13
function asyncFunction() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('Async Hello world');
}, 16);
});
}

asyncFunction().then(function (value) {
console.log(value); // => 'Async Hello world'
}).catch(function (error) {
console.log(error);
});

asyncFunction 这个函数会返回一个 Promise 对象, 对于这个Promise对象,我们调用它的

  • then 方法来设置resolve后的回调函数
  • catch 方法来设置发生错误时的回调函数

Promise 对象会在 setTimeout 之后的 16ms 时被resolve, 这时 then 的回调函数会被调用,并输出 'Async Hello world'
在这种情况下 catch 的回调函数并不会被执行(因为 promise 返回了 resolve)。

不过如果运行环境没有提供 setTimeout 函数的话,那么上面代码在执行中就会产生异常,在 catch 中设置的回调函数就会被执行。

当然,像 promise.then(onFulfilled, onRejected) 的方法声明一样, 如果不使用 catch 方法只使用 then 方法的话,如下所示的代码也能完成相同的工作。

1
2
3
4
5
asyncFunction().then(function (value) {
console.log(value);
}, function (error) {
console.log(error);
});

实践二:ajax,promise chain

Promise 来通过异步处理方式来获取 XMLHttpRequest(XHR) 的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();

req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
// 运行示例
var URL = "https://www.rayjune.me/get";
getURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(function onRejected(error){
console.error(error);
});

可以写成方法链的形式,更加简洁:

1
2
3
4
5
6
7
aPromise.then(function taskA(value){
// task A
}).then(function taskB(vaue){
// task B
}).catch(function onRejected(error){
console.log(error);
});

理由一:将 callback 简洁化、模式化

实际上即使使用回调方式的写法也能完成上面同样的工作,而使用 Promise 方式的话有什么优点么?

在使用 Promise 进行一步处理的时候,我们必须按照接口 规定的方法 编写处理代码。

也就是说,除 Promise 对象规定的方法 (这里的 thencatch) 以外的方法都是不可以使用的。不是像回调函数方式那样可以自己自由的定义回调函数的参数,而是必须严格遵守固定、统一的编程方式来编写代码。

Promise 的功能是可以将复杂的异步处理轻松地进行模式化

理由二:异步错误的捕捉

JavaScript 的异常机制在同步的情况下工作良好,但在异步的情况下则需要你在每一个异步函数完成后手动检查错误,因为这些异步函数并不在同一个调用栈上运行,所以不会像异常一样逐级传递(联想 JavaScript 的 Event Loop 机制)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try
input2 = syncTask1 input
console.log syncTask2 input2
catch err
console.error err

asyncTask1 input, (err, input2) ->
if err
console.error err
else
asyncTask2 input2, (err, output) ->
if err
console.error error err
else
console.log output

当出现错误时,错误会被逐级传递,我们只需在最后一步捕捉错误即可。

1
2
3
4
5
6
7
8
task1(input).then (input2) ->
task2 input2
.then (input3) ->
task3 input3
.then (output) ->
console.log output
, (err) ->
console.error err

Promise 的实现

从实现的角度来将,Promise 风格的函数总是返回一个 Promise 的实例,Promise 的实例是对「一项任务」的抽象。

任务可以有三种状态:

  • 正在进行(pending)
  • 成功(resolved)
  • 错误(rejected)

任务的结果(成功时)或错误(错误时)会被保存为这个对象的内部状态,外部只能通过 then 来与这项任务的结果进行交互。

当使用 then 来绑定回调函数时,如果这项任务已经完成,则直接使用内部保存的结果来通知回调函数;若这项任务还未完成,则将回调函数放入队列中,等待任务完成再进行通知。

这种设计实际上是一种订阅 / 通知模型,Promise 对象负责维护订阅关系,而任务和回调函数本身是不存在联系的。

Bonus:Are promise callbacks sent to Event queue?

Promise callbacks are simple javascript callbacks, so, as each simple javascript callback, even promise callbacks are attached to the event loop.

Reference & Thanks

Leancloud 的推荐:
https://leancloud.cn/docs/leanengine_cloudfunction_guide-node.html#hash-1420987692

Promise 迷你书:
http://liubin.org/promises-book/

MDN 上的文档:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

阮一峰的讲解:
http://es6.ruanyifeng.com/#docs/promise

Leancloud 王子亭的文章(参考里面的异常,实现章节):
https://jysperm.me/2015/04/promise-abstract-of-async-task/

文章标题:Promise:针对异步的抽象

文章作者:RayJune

时间地点:晚上 9:56 于桂苑寝室

原始链接:https://www.rayjune.me/2018/03/05/Promise-abstract-of-async-task/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。