JavaScript Promise 学习札记(巩固篇)

转载请注明出处

原文连接 http://blog.huanghanlian.com/article/5c9524514db639147ebe3300

前言

在工作中,经常用到Promise,一直以来都没有好好的整理。一直停留在会用阶段。想通过此篇文章。能够让我对Promise理解的更深些。

学习目的:

  • 学习Promise相关内容,能熟练使用Promise模式并进行测试
  • 学习Promise适合什么、不适合什么
  • 以Promises为基础进行学习,帮助后期对学习Generator和开发nodeJs中得到好的帮助

一 什么是Promise

什么是Promise

Promise是抽象异步处理对象以及对其进行各种操作的组件。
Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。

js中最原始的异步解决方案就是回调函数

asyncOperation1(data1,function (result1) {
    asyncOperation2(data2,function(result2){
        asyncOperation3(data3,function (result3) {
            asyncOperation4(data4,function (result4) {
                //do something
            })
        })
    })
})

这种代码风格不仅可读性很差,而且在团队中很不好维护。

而Promise可以极大优化异步代码,并且能够使代码逻辑清晰。

asyncOperation1(data)
    .then(function (data1) {
        return asyncOperation2(data1)
    })
    .then(function(data2){
        return asyncOperation3(data2)
    })
    .then(function(data3){
        return asyncOperation(data3)
    })

Promise简介

Promise工作流程

function asyncFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const random = Math.floor(Math.random() * 10);
            random > 5
            ?resolve('success')
            : reject('error')
        }, 16);
    });
}
asyncFunction()
.then((value) => {
    console.log(value);
})
.catch((error) => {
    console.log(error);
});

promise构造器 只接收一个参数,该参数被称为执行器(executor)的函数。该函数会被传递两个参数(方法),一个叫做resolve,另一个叫做reject。

resolve函数在成功时调用,reject函数在失败时被调用。并且resolve和reject只能被使用一次,如果之后还有resolve和reject也不会被执行了,有点儿类似于return,但是不同点在于,其他代码还会被照常执行。

new Promise构造器之后,会返回一个promise对象

对于这个promise对象,我们调用它的then 方法来设置resolve后的回调函数, catch 方法来设置发生错误时reject的回调函数。

以上promise对象会在setTimeout之后的16ms时。随机模拟成功或者失败的调用

如果只想处理错误方法

asyncFunction()
.then(null,(error) => {
    console.log(error);
})
//同等于
asyncFunction()
.catch((error) => {
    console.log(error);
})

如果不使用 catch 方法只使用 then 方法的话,下面的代码也能完成相同的工作。

asyncFunction()
.then((value) => {
    console.log(value);
})
.catch((error) => {
    console.log(error);
});

// 等同于

asyncFunction()
.then((value) => {
    console.log(value);
},(error) => {
    console.log(error);
})

但是 通过catch 处理错误是大家比较常用的。因为这样的书写方式,以及在阅读上会比较清楚。
也可以.then很多次最后再来统一来做个.catch

asyncFunction()
.then((value) => {
    console.log(value);
})
.catch((error) => {
    console.log(error);
});

还有一个好处就是说,假设你的 .then(onFulfilled) onFulfilled成功处理函数中抛了异常。
在它后面的.catch能获取到。
而如果在在.then(onFulfilled, onRejected)这样写法中。onRejected错误处理函数是无法获取的。

asyncFunction()
.then((value) => {
    console.log(value);                 //success
    throw new Error('err 发生了意外');
})
.catch((error) => {
    console.log(error);                 //Error: err 发生了意外
});
asyncFunction()
.then((value) => {
    console.log(value);                  //success
    throw new Error('err 发生了意外');
},(error) => {
    //在这里不能捕获到.then抛得错误
    console.log(error);
})

Promise的状态

new Promise 实例化的promise对象有以下三个状态。

"has-resolution" - Fulfilled
resolve(成功)时。此时会调用 onFulfilled
"has-rejection" - Rejected
reject(失败)时。此时会调用 onRejected
"unresolved" - Pending
既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等
继小鹏

编写Promise代码

创建promise对象

创建promise对象的步骤。

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

按这个流程我想通过Promise封装一个异步处理的原生请求方法

创建XHR的promise对象

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

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 = "http://blog.huanghanlian.com/";
getURL(URL)                         // => 返回promise对象
.then((value) => {                  // promise对象被 resolve 时的处理(onFulfilled)
    console.log(value);
})
.catch((error) => {
    console.error(error);           // promise对象被 reject 时的处理(onRejected)
});

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

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

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

二 实战Promise

Promise.resolve

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

new Promise的快捷方式

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

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

//例子1
let resInstance=new Promise(function(resolve){
    resolve(42);
});
resInstance.then((res)=>{
    console.log(res);//42
})

// 同等于

//例子2
let resStatic=Promise.resolve(42);
resStatic.then((res)=>{
    console.log(res);//42
})

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

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

Promise.resolve方法的参数分成四种情况。

(1)参数是一个 Promise 实例

如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

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();
    });
}

//getURL 是 Promise 实例
Promise.resolve(getURL("http://www.huanghanlian.com/data_location/list.json"))
.then((res)=>{
    console.log(res);
})
.catch((error)=>{
    console.log('捕获错误')
    console.log(error);
})

//如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

(2)参数是一个thenable对象

thenable对象指的是具有then方法的对象,比如下面这个对象。

let thenable = {
    then: function(resolve, reject) {
        resolve(42);
    }
};

Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

let thenable = {
    then: function(resolve, reject) {
        resolve(42);
    }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
    console.log(value); // 42
});

上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42

(3)参数不是具有then方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved

const p = Promise.resolve('Hello');

p.then(function(s) {
    console.log(s)         // Hello
});

上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。

(4)不带有任何参数

Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法。

const p = Promise.resolve();

p.then(() => {
    // 做我该做的事情...
});

上面代码的变量p就是一个 Promise 对象。

需要注意的是,立即resolvePromise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。

setTimeout(function() {
    console.log('three');
}, 0);

Promise.resolve().then(function() {
    console.log('two');
});

console.log('one');

// one
// two
// three

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。

Promise.reject

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

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

Promise.reject(new Error("出错了"))
.catch(error => {
    console.log(error)
})
//同等于
new Promise(function(resolve, reject) {
    reject(new Error("出错了"));
})
.catch(error => {
    console.log(error)
})

注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

const thenable = {
    then(resolve, reject) {
        reject('出错了');
    }
};

Promise.reject(thenable)
    .catch(e => {
        console.log(e === thenable)
    })
// true

上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。

Promise只能进行异步操作?

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

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

var 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

//inner promise
//outer promise
//42

从上面代码执行的结果看出

js是会按照文件由上而下执行,所以最开始执行Promise实例。
给这个Promise实例传入执行者函数,它是同步的,所以会先打印1
紧接着立刻执行了resolve(42)方法,这时候 promise 对象的已经变为确定状态,FulFilled被设置为了 42

promise.then中注册了成功后的回调。这块是问题的所在。

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

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

因此 2 会最先被调用,最后才会调用回调函数 3 。为什么要对明明可以以同步方式进行调用的函数,非要使用异步的调用方式呢?

同步调用和异步调用同时存在导致的混乱

其实在Promise之外也存在这个问题,这里我们以一般的使用情况来考虑此问题。

这个问题的本质是接收回调函数的函数,会根据具体的执行情况,可以选择是以同步还是异步的方式对回调函数进行调用。

我们以 onReady(fn) 为例进行说明,这个函数会接收一个回调函数进行处理。

function onReady(fn) {
    var readyState = document.readyState;
    if (readyState === 'interactive' || readyState === 'complete') {
        fn();
    } else {
        window.addEventListener('DOMContentLoaded', fn);
    }
}
onReady(function() {
    console.log('DOM fully loaded and parsed');
});

上面的代码会根据执行时DOM是否已经装载完毕来决定是对回调函数进行同步调用还是异步调用。

如果在调用onReady之前DOM已经载入的话
对回调函数进行同步调用

如果在调用onReady之前DOM还没有载入的话
通过注册 DOMContentLoaded 事件监听器来对回调函数进行异步调用

因此,如果这段代码在源文件中出现的位置不同,在控制台上打印的log消息顺序也会不同。

为了解决这个问题,我们可以选择统一使用异步调用的方式。

function onReady(fn) {
    var readyState = document.readyState;
    if (readyState === 'interactive' || readyState === 'complete') {
        setTimeout(fn, 0);
    } else {
        window.addEventListener('DOMContentLoaded', fn);
    }
}
onReady(function() {
    console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');

在日常开发中

  • 绝对不能对异步回调函数(即使在数据已经就绪)进行同步调用。
  • 如果对异步回调函数进行同步调用的话,处理顺序可能会与预期不
    符,可能带来意料之外的后果。
  • 对异步回调函数进行同步调用,还可能导致栈溢出或异常处理错乱等问题。
  • 如果想在将来某时刻调用异步回调函数的话,可以使用 setTimeout
    等异步API。

前面我们看到的 promise.then 也属于此类,为了避免上述中同时使用同步、异步调用可能引起的混乱问题,Promise在规范上规定 Promise只能使用异步调用方式 。

最后,如果将上面的 onReady 函数用Promise重写的话,代码如下面所示。

function onReadyPromise() {
    return new Promise(function(resolve, reject) {
        var readyState = document.readyState;
        if (readyState === 'interactive' || readyState === 'complete') {
            resolve();
        } else {
            window.addEventListener('DOMContentLoaded', resolve);
        }
    });
}
onReadyPromise().then(function() {
    console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');

由于Promise保证了每次调用都是以异步方式进行的,所以我们在实际编码中不需要调用 setTimeout 来自己实现异步调用。

Promise.then

Promise里可以将任意个方法连在一起作为一个方法链(method chain)。

promise可以写成方法链的形式

aPromise.then(function taskA(value) {
    // task A
}).then(function taskB(vaue) {
    // task B
}).catch(function onRejected(error) {
    console.log(error);
})

如果把在 then 中注册的每个回调函数称为task的话,那么我们就可以通过Promise方法链方式来编写能以taskA → task B 这种流程进行处理的逻辑

promise chain(promise 链)

写一个较长的链式promise

function taskA() {
    console.log("Task A");
}

function taskB() {
    console.log("Task B");
}

function onRejected(error) {
    console.log("Catch Error: A or B", error);
}

function finalTask() {
    console.log("Final Task");
}
var promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected)
    .then(finalTask);

上面代码中的promise chain的执行流程,如果用一张图来描述一下的话,像下面的图那样。

继小鹏

在 上面代码 中,没有为 then 方法指定第二个参数(onRejected),也可以像下面这样来理解。

then
    注册onFulfilled时的回调函数
catch
    注册onRejected时的回调函数

再看一下 上面的流程图 的话,我们会发现 Task A 和 Task B 都有指向 onRejected 的线出来。

这些线的意思是在 Task A 或 Task B 的处理中,在下面的情况下就会调用 onRejected 方法。

  • 发生异常的时候
  • 返回了一个Rejected状态的promise对象

Promise中的处理习惯上都会采用 try-catch 的风格,当发生异常的时候,会被 catch 捕获并被由在此函数注册的回调函数进行错误处理。

另一种异常处理策略是通过 返回一个Rejected状态的promise对象 来实现的,这种方法不通过使用 throw 就能在promise chain中对 onRejected 进行调用。

此外在promise chain中,由于在 onRejectedFinal Task 后面没有 catch 处理了,因此在这两个Task中如果出现异常的话将不会被捕获

Task A产生异常的例子

function taskA() {
    console.log("Task A");
    throw new Error("throw Error @ Task A")
}

function taskB() {
    console.log("Task B"); // 不会被调用
}

function onRejected(error) {
    console.log(error); // => "throw Error @ Task A"
}

function finalTask() {
    console.log("Final Task");
}
var promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected)
    .then(finalTask);
继小鹏

执行这段代码会发现 Task B 是不会被调用的。

使用reject而不是throw

Promise的构造函数,以及被 then 调用执行的函数基本上都可以认为是在 try...catch代码块中执行的,所以在这些代码中即使使用 throw ,程序本身也不会因为异常而终止。

如果在Promise中使用 throw 语句的话,会被 try...catch 住,最终promise对象也变为Rejected状态。

var promise = new Promise(function(resolve, reject) {
    throw new Error("message");
});
promise.catch(function(error) {
    console.error(error); // => "message"
});

代码像这样其实运行时倒也不会有什么问题,但是如果想把 promise对象状态 设置为Rejected状态的话,使用 reject 方法则更显得合理

var promise = new Promise(function(resolve, reject) {
    reject(new Error("message"));
});
promise.catch(function(error) {
    console.error(error); // => "message"
})

其实也可以这么来考虑,在出错的时候我们并没有调用 throw 方法,而是使用了reject ,那么给 reject 方法传递一个Error类型的对象也就很好理解了。

promise chain 中如何传递参数

前面例子中的Task都是相互独立的,只是被简单调用而已。
这时候如果 Task A 想给 Task B 传递一个参数该怎么办呢?
答案非常简单,那就是在 Task A 中 return 的返回值,会在 Task B 执行时传给它。

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

function doubleUp(value) {
    return value * 2;
}

function output(value) {
    console.log(value); // => (1 + 1) * 2
}
var 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 中打印结果
继小鹏

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

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

也就是说, Promise#then 不仅仅是注册一个回调函数那么简单,它还会将回调函数的返回值进行变换,创建并返回一个promise对象。

Promise.catch

实际上 Promise#catch 只是 promise.then(undefined, onRejected);方法的一个别名而已。 也就是说,这个方法用来注册当promise对象状态变为Rejected时的回调函数。

IE8的问题

继小鹏

上面的这张图,是下面这段代码在使用 polyfill25 的情况下在个浏览器上执行的结果

Promise#catch的运行结果

var promise = Promise.reject(new Error("message"));

promise.catch(function(error) {
    console.error(error);
});

如果我们在各种浏览器中执行这段代码,那么在IE8及以下版本则会出现 identifier notfound 的语法错误

实际上这和 catch 是ECMAScript的 保留字有关

ECMAScript 3中保留字是不能作为对象的属性名使用的。 而IE8及以下版本都是基于ECMAScript 3实现的,因此不能将 catch 作为属性来使用,也就不能编写类似promise.catch() 的代码,因此就出现了 identifier not found 这种语法错误了。

解决Promise#catch标识符冲突问题

var promise = Promise.reject(new Error("message"));
promise["catch"](function(error) {
    console.error(error);
});

或者不单纯的使用 catch ,而是使用 then 也是可以避免这个问题的。

使用Promise#then代替Promise#catch

var promise = Promise.reject(new Error("message"));
promise.then(undefined, function(error) {
    console.error(error);
});

很多压缩工具自带了将 promise.catch 转换为 promise["catch"] 的功能, 所以可能不经意之间已经解决这个问题。

如果需要支持IE8及以下版本的浏览器的话,那么一定要将这个 catch 问题牢记在心中。

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

从代码上看,aPromise.then(...).catch(...) 像是针对最初的 aPromise 对象进行了一连串的方法链调用。

然而实际上不管是 then 还是 catch 方法调用,都返回了一个新的promise对象。

var aPromise = new Promise(function(resolve) {
    resolve(100);
});
var thenPromise = aPromise.then(function(value) {
    console.log(value);
});
var catchPromise = thenPromise.catch(function(error) {
    console.error(error);
});
console.log(aPromise !== thenPromise); // => true
console.log(thenPromise !== catchPromise); // => true

=== 是严格相等比较运算符,我们可以看出这三个对象都是互不相同的,这也就证明了 thencatch 都返回了和调用者不同的promise对象。

知道了 then 方法每次都会创建并返回一个新的promise对象的话,那么我们就应该不难理解下面代码中对 then 的使用方式上的差别了。

// 1: 对同一个promise对象同时调用 `then` 方法
var aPromise = new Promise(function(resolve) {
    resolve(100);
});
aPromise.then(function(value) {
    return value * 2;
});
aPromise.then(function(value) {
    return value * 2;
});
aPromise.then(function(value) {
    console.log("1: " + value); // => 100
})
// vs
// 2: 对 `then` 进行 promise chain 方式进行调用
var bPromise = new Promise(function(resolve) {
    resolve(100);
});
bPromise.then(function(value) {
    return value * 2;
}).then(function(value) {
    return value * 2;
}).then(function(value) {
    console.log("2: " + value); // => 100 * 2 * 2
});

1种写法中并没有使用promise的方法链方式,这在Promise中是应该极力避免的写法。这种写法中的 then 调用几乎是在同时开始执行的,而且传给每个 then 方法的value 值都是 100

2中写法则采用了方法链的方式将多个 then 方法调用串连在了一起,各函数也会严格按照 resolve → then → then → then 的顺序执行,并且传给每个 then 方法的 value的值都是前一个promise对象通过 return 返回的值。

下面是一个由方法1中的 then 用法导致的比较容易出现的很有代表性的反模式的例子。

then 的错误使用方法

function badAsyncCall() {
    var promise = Promise.resolve();
    promise.then(function() {
        // 任意处理
        return newVar;
    });
    return promise;
}

这种写法有很多问题,首先在 promise.then 中产生的异常不会被外部捕获,此外,也不能得到 then 的返回值,即使其有返回值。

由于每次 promise.then 调用都会返回一个新创建的promise对象,因此需要像上述方式2那样,采用promise chain的方式将调用进行链式化,修改后的代码如下所示。

then 返回返回新创建的promise对象

function anAsyncCall() {
    var promise = Promise.resolve();
    return promise.then(function() {
        // 任意处理
        return newVar;
    });
}

Promise.all

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

使用例子

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();
    });
}

Promise.all([
        getURL("http://www.huanghanlian.com/data_location/list.json"),
        getURL("http://www.huanghanlian.com/data_location/list.json"),
        getURL("http://www.huanghanlian.com/data_location/list.json"),
    ])
    .then(function(res) {
        console.log(res)
    }).catch(function(error) {
        console.log(error)
    });

上面代码中,promises是包含 三个 个 Promise 实例的数组,只有这 3 个实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

const p1 = new Promise((resolve, reject) => {
        resolve('hello');
    })
    .then(result => result)
    .catch(e => e);

const p2 = new Promise((resolve, reject) => {
        throw new Error('报错了');
    })
    .then(result => result)
    .catch(e => e);

Promise.all([p1, p2])
    .then(result => console.log(result))
    .catch(e => console.log(e));
// ["hello", Error: 报错了]

上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。

Promise.race

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

Promise.all 在接收到的所有的对象promise都变为 FulFilled 或者 Rejected 状态之后才会继续进行后面的处理, 与之相对的是 Promise.race 只要有一个promise对象进入FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

Promise.all时的例子一样,来看一个带计时器的 Promise.race 的使用例子。

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(delay);
        }, delay);
    });
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
])
.then(function(value) {
    console.log(value); // => 1
});

创建了4个promise对象,这些promise对象会分别在1ms,32ms,64ms和128ms后变为确定状态,即FulFilled,并且在第一个变为确定状态的1ms后, .then 注册的回调函数就会被调用,这时候确定状态的promise对象会调用 resolve(1) 因此传递给 value 的值也是1,控制台上会打印出 1 来。

再来看看在第一个promise对象变为确定(FulFilled)状态后,它之后的promise对象是否还在继续运行。

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log('执行者函数是否全部执行',delay)
            resolve(delay);
        }, delay);
    });
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
])
.then(function(value) {
    console.log(value); // => 1
});
继小鹏

在执行者函数中增加了 console.log 用来输出调试信息。

可以看出, setTimeout 方法都会执行。

Promise.race 在第一个promise对象变为Fulfilled之后,并不会取消其他promise对象的执行。

看看在第一个结果为(Rejected)状态后,它之后的promise对象是否还在继续运行。

const p = Promise.race([
    new Promise(function(resolve) {
        setTimeout(function() {
            //超时的请求是否会执行
            console.log(1)
            resolve(1);
        }, 1000);
    }),
    new Promise(function(resolve) {
        setTimeout(function() {
            //超时的请求是否会执行
            console.log(2)
            resolve(2);
        }, 2000);
    }),
    new Promise(function(resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 500)
    })
]);

p
    .then(res => {
        console.log(res)
    })
    .catch(error => {
        console.error(error)
    });
继小鹏

也是一样会去执行。

Promise实现原理

待续

基于对Promise用法不太熟悉的基础上,整理出以上内容,有觉得不太正确的地方的可以一起交流~

参考资料

JavaScript Promise迷你书(中文版)

Youtbe-ES6 讀書會 - Promise

ECMAScript 6 入门-Promise 对象

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341