这篇文章主要讲了对Promise的认识和使用方法。

1. callback hell

1
2
3
4
5
6
7
8
// 异步调用无法保证执行顺序,为了解决顺序执行,就产生了回调嵌套,这就是回调地狱
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);

callback hell 即回调地狱,它是什么呢?
“回调地狱”也叫”回调金字塔”,使用 javascript异步请求时回调是不可避免的,像上面代码中多重异步操作就会形成“回调地狱”。而nodejs 是一种单线程的事件驱动而且是非阻塞的I/O模型,而I/O模型,是异步的,这样nodejs在处理结果的时候 就需要在回调函数中执行,这样也就形成了回调地狱。如何解决“回调地狱”的问题呢?答案就是Promise。

2. Promise

2.1 Promise是什么?

一个 Promise 就是一个代表了异步操作最终完成或者失败的对象。Promise 本质上是一个绑定了回调的对象,而不是将回调传进函数内部。

Promise 的构造函数接收一个参数,是函数,并且传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,resolve是将Promise的状态置为resolved,reject是将Promise的状态置为rejected。

Promise有一下约定:

  • 在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
  • 通过 .then 形式添加的回调函数,甚至都在异步操作完成之后才被添加的函数,都会被调用。
  • 通过多次调用 .then,可以添加多个回调函数,它们会按照插入顺序并且独立运行。

因此,Promise 最直接的好处就是链式调用。

2.2 链式调用

一个常见的需求就是连续执行两个或者多个异步操作,这种情况下,每一个后来的操作都在前面的操作执行成功之后,带着上一步操作所返回的结果开始执行。我们可以通过创造一个 Promise chain 来完成这种需求。
举个栗子:then 函数会返回一个新的 Promise,跟原来的不同:

1
2
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

或者

1
const promise2 = doSomething().then(successCallback, failureCallback);

第二个对象(promise2)不仅代表doSomething()函数的完成,也代表了你传入的 successCallback 或者failureCallback 的完成,这也可能是其他异步函数返回的 Promise。这样的话,任何被添加给 promise2 的回调函数都会被排在 successCallbackfailureCallback 返回的 Promise 后面。

基本上,每一个 Promise 代表了链式中另一个异步过程的完成。

对比一开始的回调地狱,通过新式函数,我们把回调绑定到被返回的 Promise 上代替以往的做法,形成一个 Promise 链:

1
2
3
4
5
6
7
8
9
10
doSomething().then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

then里的参数是可选的,catch(failureCallback)then(null, failureCallback) 的缩略形式。

2.3 举个栗子封装原生 ajaxpromise方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// javascript
// 既可以使用回调又可以使用promise
function phttp(url, callback) {
return new Promise(function (resolve, reject) {
var oReq = new XMLHttpRequest()
// 当请求加载成功之后要调用指定的函数
oReq.onload = function () {
// 我现在需要得到这里的 oReq.responseText
callback && callback(JSON.parse(oReq.responseText))
resolve(JSON.parse(oReq.responseText))
}
oReq.onerror = function (err) {
reject(err)
}
oReq.open("get", url, true)
oReq.send()
})
}

// 应用
phttp('localhost:3000/users')
.then(function (data) {
console.log(data)
})

2.4 封装一个按顺序读取文件的例子

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
26
27
// node
var fs = require('fs')

function pReadFile(filePath) {
return new Promise(function (resolve, reject) {
fs.readFile(filePath, 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}

pReadFile('./data/a.txt')
.then(function (data) {
console.log(data)
return pReadFile('./data/b.txt')
})
.then(function (data) {
console.log(data)
return pReadFile('./data/c.txt')
})
.then(function (data) {
console.log(data)
})