Promise一些小总结

2,685 阅读8分钟

在前端开发中,有个很熟悉的词叫做“回调”,在处理一些异步的函数的时候,回调被广泛应用,但是大量用回调来编程,会出现嵌套层级过多,代码风格不规范,不清晰的问题。“Promise/A+规范”是一种很方便的异步编程方式。


使用promise进行编程有哪些好处?

  • 将复杂的异步处理轻松地进行模式化
  • 代码更清晰
  • 异常处理更方便
  • 代码链式操作,

先来一段简单的promise的代码:

var promise = getAsyncPromise("fileA.txt"); 
promise.then(function(result){
// 获取文件内容成功时的处理
}).catch(function(error){
// 获取文件内容失败时的处理
});

通过这个代码,是不是觉得promise异步编程很清晰?

下面我详细介绍下promise实用的方法。


Promise 简介

1. 构建promise对象

  • Promise类似于 XMLHttpRequest ,从构造函数 Promise 来创建一个新建新 promise 对 象作为接口。
  • 要想创建一个promise对象、可以使用 new 来调用 Promise 的构造器来进行实例化。如下:
var promise = new Promise(function(resolve, reject) { 
// 异步处理
// 处理结束后、调用resolve 或 reject
});

2. 常用的方法

  • 对通过new生成的promise对象为了设置其值在 resolve(成功) / reject(失败)时调用的回调函数 可以使用 promise.then() 实例方法。
promise.then(onFulfilled, onRejected)

//resolve(成功)时 onFulfilled 会被调用
//reject(失败)时onRejected 会被调用
//onFulfilled 、 onRejected 两个都为可选参数。
  • promise.then成功和失败时都可以使用。另外在只想对异常进行处理时可以采用promise.then(undefined, onRejected),这种方式,只指定reject时的回调函数即可。 不过这种情况下 promise.catch(onRejected) 应该是个更好的选择。
promise.catch(onRejected)

3. 静态方法

  • 包括 Promise.all() 还有 Promise.resolve() 等在内,主要都是一些对Promise进行操作的 辅助方法。

4. 来个栗子

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

栗子说明:

  1. new Promise构造器之后,会返回一个promise对象
  2. 为promise对象用设置 .then 调用返回值时的回调函数。
  3. 这个函数会返回promise对象,对于这个promise对象,我们调用的 方法来设置resolve后的回调函数,catch方法来设置发生错误时的回调函数。
  4. 在这种情况下catch的回调函数并不会被执行(因为promise返回了resolve), 不过如果运行环境没有提供setTimeout函数的话,那么上面代码在执行中就会产生异常,在catch中设置的回调函数就会被执行。

上面的代码也也可以不用catch方法,用then(resolve, reject)的形式(链式操作时最好用catch,可以统一捕捉异常):

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

Promise 状态详解

大致的了解promise处理流程,单独介绍一下promise状态

1. 大致三个状态

  • resolve(成功)时。此时会调用 onFulfilled
  • reject(失败)时。此时会调用 onRejected
  • 既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等

此处输入图片的描述

*注意:从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状 态就不会再发生任何变化。

当promise的对象状态发生变化时,用 .then 来定义只会被调用一次的函数。

编写Promise代码:

1. 创建promise对象

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

来个XHR的promise对象的栗子

function getURL(URL) {
    return new Promise(function (resolve, reject) {
        var req = new XMLHttpRequest();  //栗子就不做ie兼容了
        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://httpbin.org/get"; 
getURL(URL).then(function onFulfilled(value) {
console.log(value); }).catch(function onRejected(error) {
console.error(error); });

栗子说明:

  1. getURL 只有在通过XHR取得结果状态为200时才会调用 resolve - 也就是只有数据取 得成功时,而其他情况(取得失败)时则会调用 reject 方法。
  2. resolve(req.responseText) 在response的内容中加入了参数。 resolve方法的参数并没有特 别的规则,基本上把要传给回调函数参数放进去就可以了。 ( then 方法可以接收到这 个参数值)

6. 编写promise对象处理方法

让我们在实际中使用一下刚才创建的返回promise对象的函数

getURL("http://example.com/"); // => 返回promise对象

为promise对象添加处理方法主要有以下两种:

  • promise对象被 resolve 时的处理(onFulfilled)
  • promise对象被 reject时的处理(onRejected)
getURL(URL).then(onFulfilled, onRejected); //纯then写法
getURL(URL).then( onFulfilled).catch( onRejected);  // then chatch写法

注:一般说来,使用 .catch 来将resolve和reject处理分开来写是比较推荐的做法, 这两者的 区别会在then和catch的区别中再做详细介绍。


详解promise

1. 使用 Promise.resolve

  • Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。
//比如 Promise.resolve(42); 可以认为是以下代码的语法糖。
new Promise(function(resolve){ 
    resolve(42);
});

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

Promise.resolve(42).then(
    function (value) {
        console.log(value)  //42
    }
);

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


  • Promise.resolve方法另一个作用就是将thenable对象转换为promise对象。

    **thenable对象**:简单来说它就是一个非常类似promise的东西(*就像我们有时称具有.length方法的非数组对象为Arraylike一样,thenable指的是一个具有.then 方法的对象*。)   
    
    • 最简单的例子就是jQuery.ajax(),它的返回值就是thenable的(因为 jQuery.ajax()的返回值是jqXHRObject对象,这个对象具有 .then 方法)

    • 用Promise.resolve来转换为一个promise对象。变成了promse对象的话,就能直接使用 then 或者 catch等这些在ES6Promises里定 义的方法了。

var promise = Promise.resolve(
    $.ajax('/json/comment.json')
) // => promise对象 

promise.then(function(value){
    console.log(value); 
});

总结:简单总结一下 Promise.resolve 方法的话,可以认为它的作用就是将传递给它的参数填 充(Fulfilled)到promise对象后并返回这个promise对象。


2. 使用 Promise.reject

  • Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方 法的快捷方式。
new Promise(
    function (resolve,reject) { 
        reject(new Error("出错了"));
    }
);
// 是Promise.reject(newError("出错了"))的语法糖。
//如下:
Promise.reject(new Error("BOOM!")).catch(
    function(error){ 
        console.error(error);
    }
);

上一篇写了一部分,再写一些关于promise的笔记

Promise.then() &&链式写法

  • promise可以写成方法链的形式
aPromise.then(function taskA(value) { 
// task A
}).then(function taskB(vaue){
// task B
}).catch(function onRejected(error){ //最后catch 统一捕捉异常
console.log(error);
});

来段代码,看下链式操作的执行流程

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

上面这段代码的执行流程如下图:

此处输入图片的描述

注释: .cntch() 可以捕捉 taskA taskB出现的异常,但是 onRejected、finalTask这两个后面没有catch捕捉了,所以这两个函数出现问题不会被捕捉到。

  • 用then、catch 链式写法,要比纯then写法好!!
    • catch 可以捕捉到resolve函数里面的错误,有时候我可以主动在resolve函数里throw一个错误或者返回一个Rejected状态的promise对象(推荐后者);
    • 链式操作代码更简洁

Promise链式写法中参数如何传递

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

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

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

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

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

Promise.catch 的一些小问题

  1. IE8 兼容问题catch是ECMAScrip3中保留字是不能作为对象的属性名使用的(ie8大概是基于es3实现的)可以用Promise['catch']来调用(不过很多压缩、打包工具自带转换功能,还不错)

Promise处理多个异步任务,promise.all 上场

Promise.all 接收一个promise对象的数组作为参数,当这个数组里的所有promise对象 全部变为resolve或reject状态的时候,它才会去调用 .then 方法。

// `delay`毫秒后执行resolve 
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(delay); }, delay
        );
    }); 
}

var startDate = Date.now();
// 所有promise变为resolve后程序退出 
Promise.all([
    timerPromisefy(1),
    timerPromisefy(32), 
    timerPromisefy(64), 
    timerPromisefy(128)
]).then(function (values) { 
    console.log(Date.now() - startDate + 'ms'); // 約128ms
    console.log(values); // [1,32,64,128]
    }
);

**注:**从总用时120ms来看,传递给Promise.all的promise并不是一个个的顺序执行的,而是 同时开始、并行执行的。


Promise处理多个异步任务,Promise.race 上场

它的使用方法和Promise.all一样,接收一个promise对象数组为参数

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

Promise.race([
timerPromisefy(1), 
timerPromisefy(32), 
timerPromisefy(64), 
timerPromisefy(128)
]).then(function (value) { 
    console.log(value); // => 1 只打印出一个最先出结果的
});