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。