专栏名称: 众成翻译
翻译,求知的另一种表达
目录
相关文章推荐
Python开发者  ·  OpenAI ... ·  2 天前  
Python爱好者社区  ·  梁文锋和杨植麟,论文撞车了!! ·  3 天前  
Python爱好者社区  ·  《Machine Learning ... ·  3 天前  
Python开发者  ·  上万点赞!使用 Cursor AI 编程的 ... ·  4 天前  
51好读  ›  专栏  ›  众成翻译

给初学者的 JavaScript Promise 教程 | Scotch

众成翻译  · 掘金  ·  · 2021-02-01 17:48

正文

阅读 82

给初学者的 JavaScript Promise 教程 | Scotch

译者:cherryvenus

原文链接

Code

Demo

Javascript Promises 不难。然而,许多人刚开始接触这个概念的时候,觉得有些难以理解。因此,我写下了我是如何理解 Promise 的,用一个通俗易懂的方法。

理解 Promise

Promise 简介:

""假设你是一个 宝宝 . 你的妈妈 承诺(Promise) 你,下个礼拜她会给你一台 新手机 。""

你_不知道_,下个礼拜你是否会拿到手机。你的妈妈可以_真的给你买_一个全新的手机,或者_放你鸽子_,也有可能如果她不开心:(了就扣下了手机。

这个就是 承诺(Promise) 。一个 Promise 有3个状态。他们分别是:

  1. Promise 是 待定的(pending) : 你不知道你下个礼拜能不能拿到手机。
  2. Promise 是 已解决的(resolved) :你的妈妈真的给你买了一个全新的手机。
  3. Promise 是 被拒绝的(rejected) : 因为你妈妈不开心所以不给你手机了。

创建一个 Promise

让我们将这个转化为 JavaScript。

/* ES5 */
var isMomHappy = false;
// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // 完成了
        } else {
            var reason = new Error('妈妈不开心');
            reject(reason); // reject
        }

    }
); 
复制代码

代码本身颇具表现力。

  1. 我们用一个布尔值 isMomHappy ,来定义妈妈是否开心。

  2. 我们有一个命名为 willIGetNewPhone 的 Promise 。这个 Promise 可以是 已完成的(resolved) (如果妈妈给你买了一个新手机)或者 被拒绝的(rejected) (妈妈不开心,她没有给你买一个)。

  3. 这里有一个标准的语法来新建一个 Promise ,参考 MDN 文档 ,一个 promise 语法看上去像这样。

// promise 语法看上去像这样
new Promise(/* executor*/ function (resolve, reject) { ... } );
复制代码
  1. 你需要记住的是,当结果是正确的,在你的 promise 中调用 resolve(正确的值) 。在我们的例子中,如果妈妈很开心,我们就会拿到手机。因此,我们调用 resolve 函数和 phone 这个变量。如果我们的妈妈不开心,我们会调用 reject 函数和一个理由(reason) reject(reason)

使用 Promise

现在,我们有一个 Promise。来看看怎么使用它:

/* ES5 */
...
// 调用我们的 Promise
var askMom = function () {
    willIGetNewPhone
        .then(function (fulfilled) {
            // 太好啦, 你获得了一个新手机
            console.log(fulfilled);
         // output: { brand: 'Samsung', color: 'black' }
        })
        .catch(function (error) {
            // 好不幸,你妈妈没买手机
            console.log(error.message);
         // output: '妈妈不开心'
        });
};
askMom();
复制代码
  1. 我们有一个名为 askMom 的函数。在这个函数中,我们会使用 Promise willIGetNewPhone

  2. 一旦 Promise 被解决(resolved)或者被拒(rejected),我门希望采取些措施。我们用 .then .catch 来实现。

  3. 在我们的例子中, .then 之中有个 function(fulfilled) { ... } fulfilled 是什么? fulfilled 就是是你传入 Promise 的 resolve(your_success_value) .因此,在我们例子中就是 phone

  4. 我们在 .catch 中有 function(error){ ... } error 是什么?正如你猜测的, error 正是你传入 Promise 中的 reject(your_fail_value) 。因此,在我们的例子中就是 reason

让我们看看例子运行之后的结果吧!

Demo: jsbin.com/nifocu/1/ed…

例子

串联 Promise

Promiss 是可串联的。

也就是说,你,宝宝, 承诺(Promise) 你的小伙伴,当你妈妈给你买了手机,你就会 给他们看 新手机。

这就是另一个 Promise 啦。我们来写一个!

// 简略
...
// 第二个 promise
var showOff = function (phone) {
    return new Promise(
        function (resolve, reject) {
            var message = 'hey 伙计,我有个新 ' +
                phone.color + ' ' + phone.brand + '手机';
            resolve(message);
        }
    );
};
复制代码

说明:

  • 在这个例子中,你可能意识到我们没有调用 reject 。因为这个是可选的参数。

  • 我们可以简化这个样例就像用 Promise.resolve 代替。

// 简略
...

// 第二个 promise
var showOff = function (phone) {
    var message = 'hey 伙计,我有个新 ' +
                phone.color + ' ' + phone.brand + ' 手机';
    return Promise.resolve(message);
};
复制代码

让我们串联 Promise。你,宝宝只能在 willIGetNewPhone Promise 实现之后,才能开始 showOff Promise。

...
// 调用 Promise
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // 在这里串联
    .then(function (fulfilled) {
            console.log(fulfilled);
         // output: 'Hey 伙计, 我有一个新的黑色三星手机。'
        })
        .catch(function (error) {
            // 好不幸,你妈妈没买手机
            console.log(error.message);
         // output: '妈妈不开心'
        });
};
复制代码

串联 Promise 简单吧!

Promises 是异步的

Promise 是异步的。让我们在调用 Promise 之前和之后各打印一个信息。

// 调用我们的Ppromise
var askMom = function () {
    console.log('询问妈妈之前'); // 运行之前打印
    willIGetNewPhone
        .then(showOff)
        .then(function (fulfilled) {
            console.log(fulfilled);
        })
        .catch(function (error) {
            console.log(error.message);
        });
    console.log('询问妈妈之后'); // 运行之后打印
} 
复制代码

预计的输出序列是怎么样的?也许你预计是这样的

1\. 询问妈妈之前
2\. Hey 伙计, 我有一个新的黑色三星手机。
3\. 询问妈妈之后
复制代码

然而, 真实的输出顺序是这样的:

1\. 询问妈妈之前
2\. 询问妈妈之后
3\. Hey 朋友, 我有一个新的黑色三星手机。
复制代码

输出

为什么? 因为生命 (或者 JavaScript) 不等人。

宝宝在玩的时候等待着妈妈的承诺(promise) (新手机).不是吗? 这个我们称之为 异步(asynchronous) , 代码不会因为阻塞或等待结果而不运行. 任何想等待 Promise 之后再运行的, 你需要把他们放入 .then .

ES5, ES6/2015, ES7/Next 中的 promise

ES5 - 大多数浏览器

demo代码在 ES5 的环境(所有主流浏览器+NodeJs)中是可以运行,如果你包含了 Bluebird Promise 库。这是因为 ES5 不支持直接调用 Promise 。另一个有名的 Promise 库是 Kris Kowal 的 Q

ES6 / ES2015 - 现代浏览器, NodeJs v6

demo代码可以直接调用,因为ES6支持本地 Promise。外加,和 ES6 函数 fat arrow => ,以及 const let 搭配使用,我们可以进一步简化代码`。

这里是 ES6 代码的例子

/* ES6 */
const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => { // fat arrow
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

const showOff = function (phone) {
    const message = 'Hey 伙计, 我有个一个新' +
                phone.color + ' ' + phone.brand + '手机';
    return Promise.resolve(message);
};

// 调用我们的promise
const askMom = function () {
    willIGetNewPhone
        .then(showOff)
        .then(fulfilled => console.log(fulfilled)) // fat arrow
        .catch(error => console.log(error.message)); // fat arrow
};

askMom();
复制代码

注意,所有的 var 都用 const 代替。所有的 function(resolve, reject) 都简化为 (resolve, reject) => 。 这些改变有许多好处。阅读更多:

ES7 - 异步等待让语法看上去更整洁

ES7 引入 async await 语法。这让异步语法看上去更整洁和易于理解,而不用 .then .catch

用 ES7 的语法重写例子

/* ES7 */
const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => {
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('妈妈不开心');
            reject(reason);
        }

    }
);

// 2nd promise
async function showOff(phone) {
    return new Promise(
        (resolve, reject) => {
            var message = 'Hey 伙计, 我有一个新' +
                phone.color + ' ' + phone.brand + '手机';

            resolve(message);
        }
    );
};

// 调用 Promise
async function askMom() {
    try {
        console.log('before asking Mom');

        let phone = await willIGetNewPhone;
        let message = await showOff(phone);

        console.log(message);
        console.log('after asking mom');
    }
    catch (error) {
        console.log(error.message);
    }
}

(async () => {
    await askMom();
})();
复制代码
  1. 每当你需要在函数中返回一个 Promise 的时候,你要在函数之前添加 async

E.g. async function showOff(phone)

  1. 当你需要调用一个 promise,你需要在此之前添加 await

E.g. let phone = await willIGetNewPhone; and let message = await showOff(phone); .

  1. 使用 try { ... } catch(error) { ... } 来捕捉 Promise 错误, 被拒绝的 promise

为什么用 Promise 以及何时用他们?

为什么你需要 promise ?在 promise 之前我们是如何的?在回答这些问题之前,让我们回到基本原理。

普通函数 vs 异步函数

让我们看看这两个例子,他们都执行两个数字相加,一个用普通函数相加,一个用远程方法相加。

普通函数相加两个数字

// 正常相加数字

function add (num1, num2) {
    return num1 + num2;
}

const result = add(1, 2); // you get result = 3 immediately 
复制代码
异步函数相加两个数字
// 远程相加数字

// 调用api获得结果
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
// you get result  = ""undefined""
复制代码

如果你用普通函数相加两数字,你会马上获得结果。然而如果你发出一个远程调用来获得结果,那么你就需要等待,你不能马上得到结果。

或者这样说,你不知道会不会得到结果,因为服务器可能会性能下降,响应慢等等。你不希望因为等待着结果,让整个进程都被堵住。

调用API,下载文件,读取文件一些平时你会执行的异步操作。

Pomise 出现之前的世界: 回调(Callback)

我们一定要用 Prmoise 来做异步回调吗?不是的。优先于 promise ,我们用回调(callback)。回调(callback)仅仅是个你调用的函数,当你获得返回结果的时候。让我们修改之前的例子来获得一个回调。

// 远程相加两数字
// 调用API获得结果

function addAsync (num1, num2, callback) {
    //使用有名的 jQuery getJSON 的回调 API
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback
    const result = success; // 这里你得到 result = 3
}); 
复制代码

这个语法看上去OK,为什么我们之后需要用 Promise ?

如果你想做一系列的异步操作怎么办??

比如说,不同于一次仅仅相加两个数字,我们希望加3次。用普通的函数,我们这么做:







请到「今天看啥」查看全文