JavaScript中的then()函数是什么意思?

347

我看到的代码长这样:

myObj.doSome("task").then(function(env) {
    // logic
});
< p > then() 从何而来?


14
更新:我发现这与CommonJS的Promise API有关。http://www.sitepen.com/blog/2010/01/19/commonjsjsgi-the-emerging-javascript-application-server-platform/ (翻译):经更新,我发现这与CommonJS的Promise API有关。可参考此链接:http://www.sitepen.com/blog/2010/01/19/commonjsjsgi-the-emerging-javascript-application-server-platform/ - Kay Pale
12个回答

457

JavaScript中处理异步调用的传统方式是使用回调函数。假设我们需要按顺序向服务器发出三个请求来设置我们的应用程序。使用回调函数,代码可能会像以下示例一样(假设有一个xhrGET函数来进行服务器调用):

// Fetch some server configuration
    xhrGET('/api/server-config', function(config) {
        // Fetch the user information, if he's logged in
        xhrGET('/api/' + config.USER_END_POINT, function(user) {
            // Fetch the items for the user
            xhrGET('/api/' + user.id + '/items', function(items) {
                // Actually display the items here
            });
        });
    });

在这个例子中,我们首先获取服务器配置。然后根据它,我们获取当前用户的信息,最后获取当前用户的项目列表。每个 xhrGET 调用都需要一个回调函数,在服务器响应时执行。

当然,我们嵌套的层级越多,代码就越难以阅读、调试、维护、升级和处理。这通常被称为回调地狱。此外,如果我们需要处理错误,可能需要向每个 xhrGET 调用传递另一个函数,告诉它在出现错误时该怎么做。如果我们想要有一个共同的错误处理程序,那是不可能的。

Promise API 的设计旨在解决这个嵌套问题和错误处理问题。

Promise API 提出以下建议:

  1. 每个异步任务都将返回一个 promise 对象。
  2. 每个 promise 对象都将具有一个 then 函数,它可以接受两个参数,一个成功处理程序和一个错误处理程序。
  3. then 函数中的成功或错误处理程序将仅在异步任务完成后调用一次。
  4. then 函数还将返回一个 promise,以允许链接多个调用。
  5. 每个处理程序(成功或错误)都可以返回一个 value,这将作为下一个函数的参数,在 promise 链中传递。
  6. 如果处理程序返回一个 promise(发出另一个异步请求),则只有在该请求完成后,才会调用下一个处理程序(成功或错误)。

因此,前面的示例代码可能转换为以下内容,使用 promises 和 $http 服务(在 AngularJs 中):

$http.get('/api/server-config').then(
    function(configResponse) {
        return $http.get('/api/' + configResponse.data.USER_END_POINT);
    }
).then(
    function(userResponse) {
        return $http.get('/api/' + userResponse.data.id + '/items');
    }
).then(
    function(itemResponse) {
        // Display items here
    }, 
    function(error) {
        // Common error handling
    }
);

传播成功与错误

串联 promises 是一项非常强大的技术,它使我们能够实现许多功能,例如使服务进行服务器调用、对数据进行后处理,然后将处理后的数据返回给控制器。但是当我们使用 promise 链时,有一些事情需要记住。

考虑以下假设的 promise 链,其中包含三个 promises,P1、P2 和 P3。每个 promise 都有一个成功处理程序和一个错误处理程序,因此 P1 有 S1 和 E1,P2 有 S2 和 E2,P3 有 S3 和 E3:

xhrCall()
  .then(S1, E1) //P1
  .then(S2, E2) //P2
  .then(S3, E3) //P3

在正常情况下,没有错误的情况下,应用程序将流经S1、S2和最后的S3。但在现实生活中,事情从来不会那么顺利。P1可能会遇到错误,或者P2可能会遇到错误,触发E1或E2。

考虑以下情况:

• 在P1中,我们收到服务器的成功响应,但返回的数据不正确,或者服务器上没有数据(考虑空数组)。在这种情况下,对于下一个promise P2,它应该触发错误处理程序E2。

• 我们收到P2的错误,触发E2。但在处理程序内部,我们有来自缓存的数据,确保应用程序可以正常加载。在这种情况下,我们可能希望在E2之后调用S3。

因此,每次编写成功或错误处理程序时,我们需要进行一次调用-鉴于我们当前的函数,这个promise是下一个promise链中的成功还是失败?

如果我们想要触发promise链中下一个promise的成功处理程序,我们可以从成功或错误处理程序中返回一个值

另一方面,如果我们想要触发promise链中下一个promise的错误处理程序,我们可以使用deferred对象并调用其reject()方法

那么deferred对象是什么?

JQuery中的deferred对象表示将在以后完成的工作单元,通常是异步地。一旦工作单元完成,deferred对象可以设置为已解决或已失败。

deferred对象包含一个promise对象。通过promise对象,您可以指定在工作单元完成时要发生的事情。你通过在promise对象上设置回调函数来实现这一点。

JQuery中的Deferred对象: https://api.jquery.com/jquery.deferred/

AngularJs中的Deferred对象:https://docs.angularjs.org/api/ng/service/$q


16
写得非常好。这帮助我真正理解了承诺的含义。 - Ju66ernaut
错误处理程序,也就是第二个参数,总是可选的吗? - 1.21 gigawatts

85

4
是的,它就像一个回调函数,在任务完成时执行。它的不同之处在于...(需要上下文才能进一步翻译) - Muhammad Umer
3
在另一个评论中提到的 JavaScript Promises 表示:一个 Promise 只能成功或失败一次,如果一个 Promise 已经成功或失败,并且在之后添加了一个成功/失败回调函数,正确的回调函数将被调用。 - XoXo
此外,Promise nuggets 介绍了如何使用 promise 并说明了 callback 的作用。 - XoXo
在第一页中,有一些代码块缺失(大白空格)。大多数人会想要检查元素并在下面找到fiddle的URL。这条消息是给其他人的 - fiddles仍然有效 ;) - DanteTheSmith
1
@MuhammadUmer: 请阅读此内容https://dev59.com/VG865IYBdhLWcg3wTctY#31453579(Sid提供的答案) - SharpCoder

41

在ECMAScript6中

使用Promises,JavaScript纯粹地包含了.then()方法。

来自Mozilla文档

then()方法返回一个Promise。它接受两个参数:对于Promise成功和失败情况的回调函数。

而Promise对象则被定义为

Promise对象用于延迟和异步计算。Promise代表着一个尚未完成但将来会完成的操作。

也就是说,Promise充当着一个还未计算的值的占位符,但预计将来会被解析。而.then()函数用于将函数与Promise关联起来,当它被解析时,它可以作为成功或失败的标志。

在ECMAScript6之前

据我所知,在javascript中没有内置的then()方法(在撰写本文时)。

看起来doSome("task")返回的结果具有名为then的方法。

如果您将doSome()的返回结果记录到控制台中,您应该能够看到所返回内容的属性。

console.log( myObj.doSome("task") ); // Expand the returned object in the
                                     //   console to see its properties.

14
当时还没有内置的 .then(),但原生的 Promise 现在在 ES6 中被引入了:http://www.html5rocks.com/en/tutorials/es6/promises/ - janfoeh

26

这是我自己制作的一个东西,用于梳理事物的工作原理。我想其他人也可以从这个具体的例子中获益:

doit().then(function() { log('Now finally done!') });
log('---- But notice where this ends up!');

// For pedagogical reasons I originally wrote the following doit()-function so that 
// it was clear that it is a promise. That way wasn't really a normal way to do 
// it though, and therefore Slikts edited my answer. I therefore now want to remind 
// you here that the return value of the following function is a promise, because 
// it is an async function (every async function returns a promise). 
async function doit() {
  log('Calling someTimeConsumingThing');
  await someTimeConsumingThing();
  log('Ready with someTimeConsumingThing');
}

function someTimeConsumingThing() {
  return new Promise(function(resolve,reject) {
    setTimeout(resolve, 2000);
  })
}

function log(txt) {
  document.getElementById('msg').innerHTML += txt + '<br>'
}
<div id='msg'></div>


7

这里有一个小的JS_Fiddle

then是一个回调方法堆栈,当Promise被解析后可以使用。它是类似于jQuery的库的一部分,但现在也适用于原生JavaScript。以下是详细说明它的工作方式。

您可以在原生JavaScript中使用Promise:就像在jQuery中有promises一样,每个promise都可以堆叠,并且可以使用Resolve和Reject回调进行调用,这就是您可以链接异步调用的方式。

我从MSDN文档中的电池充电状态中进行了fork并编辑。

这个程序的作用是尝试查找用户笔记本电脑或设备是否正在充电电池。然后调用此函数,如果成功,则可以执行后续操作。

navigator
    .getBattery()
    .then(function(battery) {
       var charging = battery.charging;
       alert(charging);
    })
    .then(function(){alert("YeoMan : SINGH is King !!");});

另一个 ES6 示例

function fetchAsync (url, timeout, onData, onError) {
    …
}
let fetchPromised = (url, timeout) => {
    return new Promise((resolve, reject) => {
        fetchAsync(url, timeout, resolve, reject)
    })
}
Promise.all([
    fetchPromised("http://backend/foo.txt", 500),
    fetchPromised("http://backend/bar.txt", 500),
    fetchPromised("http://backend/baz.txt", 500)
]).then((data) => {
    let [ foo, bar, baz ] = data
    console.log(`success: foo=${foo} bar=${bar} baz=${baz}`)
}, (err) => {
    console.log(`error: ${err}`)
})

定义 :: then是一种用于解决异步回调的方法

这是在ES6中引入的

请在此处查找适当的文档:Es6 Promises


你的回答实际上并没有回答问题,只是提供了一个API使用的例子,没有解释then来自哪里以及它是如何工作的。你应该改进你的回答,提供这些细节。 - Didier L
@TarandeepSingh - 在第一个 then 语句中,您在警报电池状态时没有返回任何 promise 对象。那么第二个 then 的用途是什么? - Mohit Jain
@MohitJain 它展示了即使没有新的 Promise,你也可以执行多个回调函数。因为多个调用也可以使用 Promise.all 来实现。 - Tarandeep Singh
你所说的“method callback stack”是什么意思? - Bergi

7

关于在箭头函数中使用花括号{}的用法:


这三个例子都在做同一件事情(什么也不做),但它们都有有效的语法,并且是一个有效的 Promise 链!


new Promise(function(ok) {
   ok( 
      /* myFunc1(param1, param2, ..) */
   )
}).then(function(){
     /* myFunc1 succeed */
     /* Launch something else */
     /* console.log(whateverparam1) */
     /* myFunc2(whateverparam1, otherparam, ..) */
}).then(function(){
     /* myFunc2 succeed */
     /* Launch something else */
     /* myFunc3(whatever38, ..) */
})

console.log("This code has no errors GG!")

使用箭头函数的简写逻辑,不需要使用 {}

new Promise((ok) =>
   ok( 
      /* myFunc1(param1, param2, ..) */
).then(() =>
     0 // HEY DID YOU NOTICE! A number that does nothing,
     // but otherwise the parsing will fail!
     // The code is pretty clean but have a major downside
     // As arrow functions without {} can contains only one declaration
     // console.log("something") will FAIL here
).then(() =>
     "" // HEY DID YOU NOTICE! An empty string that does nothing,
     // but otherwise the parsing will fail!
     // As arrow functions without {} can contains only one declaration
     // We can't add more code here, hence:
     // console.log("something")
     // Will break the whole promise
     // This is likely the error in y(our) code ;)
))

console.log("This code has no errors GG!")


带有 {} 的箭头函数

new Promise( (ok) => {
   ok( 
      /* myFunc1(param1, param2, ..) */
   )
}).then( () => {
     /* myFunc1 succeed */
     /* Launch something else */
}).then( () => {
     /* myFunc2 succeed */
     /* Launch something else */
     /* myFunc3(whatever38, ..) */
     console.log("something")
     /* More console logs! */
     console.log("something else")
})

console.log("This code has no errors GG!")


7

.then 在异步函数中返回一个 Promise。

一个好的例子如下:

var doSome = new Promise(function(resolve, reject){
    resolve('I am doing something');
});

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

为了添加另一个逻辑,您还可以添加reject('我是被拒绝的参数')调用函数并将其console.log出来。

4
我猜测doSome返回的是this,即myObj对象,该对象同时具有一个then方法。这是标准的方法串联。 如果doSome没有返回被执行的doSome对象,那么它一定会返回一个带有then方法的对象。 正如@patrick所指出的,标准的js中没有then()方法。

2
我怀疑 doSome 返回了这个值 - 没有任何证据可以证明或支持这种怀疑。 - Salathiel Genèse

1

doSome("task")必须返回一个Promise对象,并且该Promise对象始终具有then函数。因此,您的代码应该像这样:

promise.then(function(env) {
    // logic
}); 

你知道这只是一个普通的成员函数调用。


0
在这种情况下,then()是由doSome()方法返回的对象的类方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接