乐趣区

关于javascript:JavaScript-异步操作之-Promise

因为 JavaScript 通常是由单线程来执行代码,所以在编写 JavaScript 代码时常常须要应用异步操作来进步程序性能。一般来说异步执行在 JavaScript 中应用 回调函数 的模式来实现。不过近年来因为社区的推动,Promise 曾经成为 JavaScript 异步编程的一个规范,应用 Promise 进行异步编程,代码的可维护性将有很大晋升,尤其是应用 Promise 取代多层 回调函数 嵌套的问题。

Promise 简介

上面是结构一个最简略的 Promise 代码示例:

// 结构 Promise
const promise = new Promise(function (resolve, reject) {
    // 异步操作代码
    if (异步操作胜利) {resolve(this.response);
    } else {reject(this.status);
    }
});

// promise 状态扭转时的回调
promise.then(function (res) {console.log(res);  // 执行胜利回调
}, function (error) {console.log(error);  // 执行失败回调
})

Promise 是 JavaScript 提供的一个对象,能够通过 new 关键字结构这个对象。Promise 构造函数接管一个函数作为参数,该函数接管另外两个函数作为参数,别离是 resolvereject,这两个函数由 JavaScript 引擎本身提供,无需咱们手动创立。

Promise 对象有三种状态,别离是:pending(进行中)、fulfilled(已胜利)、rejected(已失败)。有两种状态扭转,一种是从 pending 变为 fulfilled,另一种是从 pending 变为 rejected,一旦状态扭转,状态就会凝固,无奈被再次更改。而 resolve 的作用就是将 Promise 对象的状态从 pending 变为 fulfilledreject 的作用是将 Promise 对象的状态从 pending 变为 rejected

通常咱们在传递给 Promise 构造函数的函数顶部编写一些异步代码,例如一个 AJAX 申请,而后当异步代码执行胜利的时候,调用 resolveresolve 是一个函数,它可能接管参数,所以咱们调用它时能够将异步代码的执行后果传递给它,此时 resolve 函数有两个作用,一是扭转 Promise 的状态为 fulfilled,二是它可能将传递进去的参数传递到 promise 实例的 then 办法的第一个回调函数中,待后续操作应用。异步代码执行失败时调用 reject 函数,此时 reject 函数同样有两个作用,一是扭转 Promise 的状态为 rejected,二是将传递进去的参数传递到 promise 实例的 then 办法的第二个回调函数中。

Promise 状态一旦扭转,咱们就能够通过 Promise 对象的实例(promise 变量)的 then 办法获取到异步操作的后果。then 办法接管两个回调函数作为参数,第一个回调函数会在 Promise 状态变为 fulfilled 的时候主动调用,第二个回调函数会在 Promise 状态变为 rejected 的时候主动调用。这两个回调函数别离接管一个参数,第一个回调函数接管到的 res 参数实际上就是咱们在 Promise 外部异步代码执行胜利时传递给 resolve 的参数,第二个回调函数接管到的 error 参数是在 Promise 外部异步代码执行失败时传递给 reject 的参数。

以上就是 Promise 对象的执行流程,接下来咱们就用一个示例来展现 Promise 在理论编写代码中如何利用。

先来看一段用 回调函数 的写法发送 AJAX 申请的代码示例:

function handleResponse() {if (this.readyState === 4) {if (this.status === 200) {console.log(this.response);
        } else {console.log(this.status);
        }
    }
}

const request = new XMLHttpRequest();
request.open('GET', 'http://httpbin.org/get');
request.onreadystatechange = handleResponse;
request.send();

在编写 Javascript 时这种代码十分常见,那么如何用 Promise 的写法来编写下面这段代码呢?请看上面的示例:

const promise = new Promise(function (resolve, reject) {function handleResponse() {if (this.readyState === 4) {if (this.status === 200) {resolve(this.response);  // 申请胜利时调用
            } else {reject(this.status);  // 申请失败时调用
            }
        }
    }
    const request = new XMLHttpRequest();
    request.open('GET', 'http://httpbin.org/get');
    request.onreadystatechange = handleResponse;
    request.send();})

promise.then(function (res) {console.log(res);  // 胜利回调
}, function (error) {console.log(error);  // 失败回调
})

以上就是通过 Promise 来解决 AJAX 的写法。乍一看,感觉代码是不是变多了,也更麻烦了?事实上,的确如此,如果仅仅从发送一个简略的 AJAX 申请代码来看咱们的确没必要采纳 Promise 的写法,这样做只会让代码看起来更加简单。

Promise 可能解决什么问题

那么 Promise 的实用场景到底是什么,可能解决什么问题呢?

  1. Promise 非常适合解决 回调天堂 问题。
  2. Promise 解决了回调函数中不可能应用 return 的问题。

咱们来看看 Promise 是如何解决这两个问题的。假如咱们须要调用一个获取用户发表的博客列表的接口,而这个接口须要登录才可能调用。也就是说咱们调用接口的程序必须是:先调用登录认证接口进行登录,而后再去调用获取用户博客列表的接口。

如果应用 jQuery 发送 AJAX 申请的 回调函数 写法,咱们有可能会写出这样的代码:

// 发送第一个 AJAX 申请进行登录
$.ajax({
    type: 'GET',
    url: 'http://localhost:3000/login',
    success: function (res) {
        // 登录胜利回调函数外部,发送第二个 AJAX 申请获取数据
        $.ajax({
            type: 'GET',
            url: 'http://localhost:3000/blog/list',
            success: function (res) {console.log(res);
            },
            error: function (xhr) {console.log(xhr.status);
            }
        });
    },
    error: function (xhr) {console.log(xhr.status);
    }
});

能够看到,应用 回调函数 模式写出的代码是嵌套模式的。也就是说,每多一次申请,咱们就要在 success 回调函数外部再写一层嵌套。如果嵌套档次过多,那么就会呈现 JavaScript 令人头疼的问题 —— 回调天堂。嵌套档次越多,代码就越难以了解,如果出现异常,那么调试将会是一个十分苦楚的过程。

咱们尝试着用 Promise 来解决下面的问题:

const getResponse = function (url) {const promise = new Promise(function (resolve, reject) {
        $.ajax({
            type: 'GET',
            url: url,
            success: function (res) {resolve(res);
            },
            error: function (xhr) {reject(xhr);
            }
        });
    });
    return promise;
}

getResponse('http://localhost:3000/login').then(function (res) {getResponse('http://localhost:3000/blog/list').then(function (res) {console.log(res);
    }, function (xhr) {console.log(xhr.status);
    })
}, function (xhr) {console.log(xhr.status);
});

以上是应用 Promise 进行重构的代码,但仔细观察你会发现:下面的代码仍然在用 回调 的思维来写 Promise 代码。在第一次调用 getResponse 函数申请 http://localhost:3000/login 胜利后,代码执行 then 办法,在 then 办法的外部,再一次调用了 getResponse 函数,这一次申请 http://localhost:3000/blog/list 地址,胜利后执行下一个 then 办法,在 then 办法外部就可能胜利获取用户博客列表的接口返回后果。

其实下面这段代码同样存在 回调天堂 的问题,因为如果申请次数过多,咱们同样须要在一层层的嵌套函数中调用 getResponse。这无疑没有解脱 回调天堂

来看正确应用 Promise 对象解决 回调天堂 的示例:

const getResponse = function (url) {const promise = new Promise(function (resolve, reject) {
        $.ajax({
            type: 'GET',
            url: url,
            success: function (res) {resolve(res);
            },
            error: function (xhr) {reject(xhr.status);
            }
        });
    });
    return promise;
}

getResponse('http://localhost:3000/login').then(function (res) {return getResponse('http://localhost:3000/blog/list');
}, function (xhr) {console.log(xhr.status);
}).then(function (res) {console.log(res);
}, function (xhr) {console.log(xhr.status);
});

Promise 接口中,then 办法会返回一个新的 Promise 实例,所以咱们可能采纳链式调用的写法,then 办法前面再接另一个 then 办法,实践上能够有限的接下去。

留神第一个 then 办法的外部,在调用 getResponse('http://localhost:3000/blog/list') 的后面加上了 return。正是这个 return 的存在,才使得不应用层层嵌套的写法成为可能,得以使代码展平。Promise 实例前面的 then 办法会顺次执行,前一次 then 办法外部的 return 后果,会当作参数传入下一个 then 办法。如果应用回调函数的写法,因为其外部没有方法 return,所以你只能有限的减少嵌套来解决问题。

这段代码能力体现出 Promise 对象的真正威力,它可能把层层嵌套的代码全副展平。每多一次申请,就在前面再减少一个 then办法的调用,这样写进去的代码显著要直观很多,你不须要再写出多层嵌套的代码。

Promisethen 办法和 catch 办法

通过后面的介绍咱们晓得 Promisethen 办法接管两个回调函数作为参数。实际上,它的第二个参数并不是必填的,如果你不关怀异样,那么就能够只写第一个回调函数。

Promise 还提供了 catch 办法专门用来接管异样,写法如下:

getResponse('http://localhost:3000/login').then(function (res) {return getResponse('http://localhost:3000/blog/list');
}).catch(function (xhr) {console.log(xhr.status);
});

用下面这种写法来解决异样更合乎直觉,看起来也更加清晰,不必在 then 办法外部传入第二个回调函数去解决异样,而是独自在 catch 办法外部进行解决。事实上 .catch(callback) 办法等价于 .then(undefined, callback)

Promiseall 办法和 race 办法

Promiseall 办法能够将多个 Promise 实例,合并为一个 Promise 实例。

这有什么用?假如咱们须要期待多个异步操作都执行完能力执行下一步逻辑,此时就是应用 all 办法的绝佳机会。这种状况应用 回调函数 的模式不太好解决,而应用 Promise 就非常简单。

咱们通过申请三次 http://httpbin.org/get 来模仿须要期待的三个异步操作,写出的代码如下:

const p1 = getResponse('http://httpbin.org/get');
const p2 = getResponse('http://httpbin.org/get');
const p3 = getResponse('http://httpbin.org/get');

const p = Promise.all([p1, p2, p3]);
p.then(res => {console.log(res);
}).catch(err => {console.log(err);
});

all 办法接管一个数组作为参数,数组每一项都是一个 Promise 对象,Promise 实例 p 就是合并后的对象,p1p2p3 的状态决定了 p 的最终状态。

只有 p1p2p3 的状态同时都变为 fulfilled 时,p 的状态才会变为 fulfilled。只有 p1p2p3 的状态有一个变为 rejectedp 的状态就会变为 rejected。当 p 的状态变为 fulfilled时,p1p2p3 的返回后果会组成一个列表,主动传递给 pthen 办法的回调函数。并且这个列表的程序由传入 all 办法的数组中 p1p2p3 的程序决定。

p1p2p3 任意一个状态变为 rejected 时,只有其没有本人的 catch 办法时才会调用 all 办法前面的 catch 办法。否则只会调用其本人的 catch 办法。

至于 race 办法的用法与 all 办法完全相同,其区别是:只有 p1p2p3 的状态有一个变为 fulfilled 时,p 的状态就会变为 fulfilled。这也是 race 这个单词所代表的含意 竞争,只有 p1p2p3 其中有一个对象率先扭转了状态,那么 p 的状态也就随之扭转并且凝固。其余两个对象外部的代码还是会执行,但其后果曾经没有用了。

因为应用 race 的代码只须要将 Promise.all([p1, p2, p3]) 换成 Promise.race([p1, p2, p3]) 即可,所以这里不再给出代码示例。

以上就是对于 Promise 应用办法的介绍,事实上 Promise 不止提供了这些接口,不过其余接口很少会用到,所以这里不再过多介绍,等你相熟了它的用法后能够再进一步去学习。

首发地址:https://jianghushinian.cn/

退出移动版