RN-《Promise迷你书》

Promise workflow

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 中设置的回调函数就会被执行。

new Promise

创建promise对象的流程如下所示。

  1. new Promise(fn) 返回一个promise对象
  2. fn 中指定异步等处理
    • 处理结果正常的话,调用resolve(处理结果值)
    • 处理结果错误的话,调用reject(Error对象)

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

创建XHR的promise对象

首先,创建一个用Promise把XHR处理包装起来的名为 getURL 的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function getURL(URL) {
return new Promise(function (resolve, reject) {
let 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();
});
}
// 运行示例
let URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value) {
console.log(value);
}).catch(function onRejected(error) {
console.error(error);
});

getURL 只有在通过XHR取得结果状态为200时才会调用 resolve - 也就是只有数据取得成功时,而其他情况(取得失败)时则会调用 reject 方法。

resolve(req.responseText) 在response的内容中加入了参数。 resolve方法的参数并没有特别的规则,基本上把要传给回调函数参数放进去就可以了。 ( then 方法可以接收到这个参数值)

熟悉Node.js的人,经常会在写回调函数时将 callback(error, response) 的第一个参数设为error对象,而在Promise中resolve/reject则担当了这个职责(处理正常和异常的情况),所以 在resolve方法中只传一个response参数是没有问题的。

接下来我们来看一下reject函数。

XHR中 onerror 事件被触发的时候就是发生错误时,所以理所当然调用reject。 这里我们重点来看一下传给reject的值。

发生错误时要像这样 reject(new Error(req.statusText)); ,创建一个Error对象后再将具体的值传进去。 传给reject 的参数也没有什么特殊的限制,一般只要是Error对象(或者继承自Error对象)就可以。

传给reject 的参数,其中一般是包含了reject原因的Error对象。 本次因为状态值不等于200而被reject,所以reject 中放入的是statusText。 (这个参数的值可以被 then 方法的第二个参数或者 catch 方法中使用)

Promise.resolve

一般情况下我们都会使用 new Promise() 来创建promise对象,但是除此之外我们也可以使用其他方法。

静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。

比如 Promise.resolve(42); 可以认为是以下代码的语法糖。

1
2
3
4
5
new Promise(function (resolve) {
resolve(42);
}).then(function (value) {
console.log(value);
});

在这段代码中的 resolve(42); 会让这个promise对象立即进入确定(即resolved)状态,并将 42 传递给后面then里所指定的 onFulfilled 函数。

方法 Promise.resolve(value); 的返回值也是一个promise对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。

1
2
3
Promise.resolve(42).then(function (value) {
console.log(value);
});

Promise.resolve 作为 new Promise() 的快捷方式,在进行promise对象的初始化或者编写测试代码的时候都非常方便。

Promise.reject

Promise.reject(error)是和 Promise.resolve(value)类似的静态方法,是 new Promise() 方法的快捷方式。

比如 Promise.reject(new Error("出错了")) 就是下面代码的语法糖形式。

1
2
3
new Promise(function (resolve, reject) {
reject(new Error("出错了"));
});

下面这段代码的功能是调用该promise对象通过then指定的 onRejected 函数,并将错误(Error)对象传递给这个 onRejected 函数。

1
2
3
Promise.reject(new Error("BOOM!")).catch(function (error) {
console.error(error);
});

Promise只能进行异步操作?

在使用Promise.resolve(value) 等方法的时候,如果promise对象立刻就能进入resolve状态的话,那么你是不是觉得 .then 里面指定的方法就是同步调用的呢?

实际上, .then 中指定的方法调用是异步进行的。

1
2
3
4
5
6
7
8
let promise = new Promise(function (resolve) {
console.log("inner promise"); // 1
resolve(42);
});
promise.then(function (value) {
console.log(value); // 3
});
console.log("outer promise"); // 2

执行上面的代码会输出下面的log,从这些log我们清楚地知道了上面代码的执行顺序。

1
2
3
inner promise // 1
outer promise // 2
42 // 3

由于JavaScript代码会按照文件的从上到下的顺序执行,所以最开始 <1> 会执行,然后是 resolve(42); 被执行。这时候 promise 对象的已经变为确定状态,FulFilled被设置为了 42

下面的代码 promise.then 注册了 <3> 这个回调函数,这是本专栏的焦点问题。

由于 promise.then 执行的时候promise对象已经是确定状态,从程序上说对回调函数进行同步调用也是行得通的。

但是即使在调用 promise.then 注册回调函数的时候promise对象已经是确定的状态,Promise也会以异步的方式调用该回调函数,这是在Promise设计上的规定方针。

因此 <2> 会最先被调用,最后才会调用回调函数 <3>

为了避免同时使用同步、异步调用可能引起的混乱问题,Promise在规范上规定 Promise只能使用异步调用方式

promise chain 中传递参数

这时候如果 Task A 想给 Task B 传递一个参数该怎么办呢?

答案非常简单,那就是在 Task A 中 return 的返回值,会在 Task B 执行时传给它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function doubleUp(value) {
return value * 2;
}

function increment(value) {
return value + 1;
}

function output(value) {
console.log(value); // => (1 + 1) * 2
}

let promise = Promise.resolve(1);
promise
.then(increment)
.then(doubleUp)
.then(output)
.catch(function (error) {
// promise chain中出现异常的时候会被调用
console.error(error);
});

这段代码的入口函数是 Promise.resolve(1); ,整体的promise chain执行流程如下所示。

  1. Promise.resolve(1); 传递 1 给 increment 函数
  2. 函数 increment 对接收的参数进行 +1 操作并返回(通过return
  3. 这时参数变为2,并再次传给 doubleUp 函数
  4. 最后在函数 output 中打印结果

每次调用then都会返回一个新创建的promise对象

每个方法中 return 的值不仅只局限于字符串或者数值类型,也可以是对象或者promise对象等复杂类型。

return的值会由 Promise.resolve(return的返回值); 进行相应的包装处理,因此不管回调函数中会返回一个什么样的值,最终 then 的结果都是返回一个新创建的promise对象。

1
2
3
4
5
6
7
8
9
10
11
12
let aPromise = new Promise(function (resolve) {
resolve(100);
});
let thenPromise = aPromise.then(function (value) {
console.log(value); // 100
});
let catchPromise = thenPromise.catch(function (error) {
console.error(error);
});
console.log(aPromise !== thenPromise); // => true
console.log(thenPromise !== catchPromise); // => true
// 打印顺序为 true true 100

Promise API

Promise.then

promise.then(onFulfilled, onRejected);

1
2
3
4
5
6
7
8
let promise = new Promise(function (resolve, reject) {
resolve("传递给then的值");
});
promise.then(function (value) {
console.log(value); // 传递给then的值
}, function (error) {
console.error(error);
});

这段代码创建一个promise对象,定义了处理onFulfilled和onRejected的函数(handler),然后返回这个promise对象。

这个promise对象会在变为resolve或者reject的时候分别调用相应注册的回调函数。

  • 当handler返回一个正常值的时候,这个值会传递给promise对象的onFulfilled方法。
  • 定义的handler中产生异常的时候,这个值则会传递给promise对象的onRejected方法。

Promise.catch

promise.catch(onRejected);

1
2
3
4
5
6
7
8
let promise = new Promise(function (resolve, reject) {
resolve("传递给then的值");
});
promise.then(function (value) {
console.log(value); // 传递给then的值
}).catch(function (error) {
console.error(error);
});

这是一个等价于promise.then(undefined, onRejected) 的语法糖。

Promise.resolve

Promise.resolve(promise);
Promise.resolve(thenable);
Promise.resolve(object);

1
2
3
4
5
6
7
8
9
10
11
12
let taskName = "task 1";
asyncTask(taskName).then(function (value) {
console.log(value); // Done! task 1
}).catch(function (error) {
console.error(error);
});

function asyncTask(name) {
return Promise.resolve(name).then(function (value) {
return "Done! " + value;
});
}

根据接收到的参数不同,返回不同的promise对象。

虽然每种情况都会返回promise对象,但是大体来说主要分为下面3类。

  • 接收到promise对象参数的时候

    返回的还是接收到的promise对象

  • 接收到thenable类型的对象的时候

    返回一个新的promise对象,这个对象具有一个 then 方法

  • 接收的参数为其他类型的时候(包括JavaScript对或null等)

    返回一个将该对象作为值的新promise对象

Promise.reject

Promise.reject(object)

返回一个使用接收到的值进行了reject的新的promise对象。

而传给Promise.reject的值也应该是一个 Error 类型的对象。

另外,和 Promise.resolve不同的是,即使Promise.reject接收到的参数是一个promise对象,该函数也还是会返回一个全新的promise对象。

1
2
var r = Promise.reject(new Error("error"));
console.log(r === Promise.reject(r));// false

Promise.all

Promise.all(promiseArray);

1
2
3
4
5
6
let p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
console.log(results); // [1, 2, 3]
});

生成并返回一个新的promise对象。

参数传递promise数组中所有的promise对象都变为resolve的时候,该方法才会返回, 新创建的promise则会使用这些promise的值。

如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的promise对象。

由于参数数组中的每个元素都是由 Promise.resolve 包装(wrap)的,所以Paomise.all可以处理不同类型的promose对象。

Promise.race

Promise.race(promiseArray);

1
2
3
4
5
6
let p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.race([p1, p2, p3]).then(function (value) {
console.log(value); // 1
});

生成并返回一个新的promise对象。

参数 promise 数组中的任何一个promise对象如果变为resolve或者reject的话, 该函数就会返回,并使用这个promise对象的值进行resolve或者reject。